Skip to content
3 changes: 2 additions & 1 deletion src/autocomplete/ResourceSectionCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export class ResourceSectionCompletionProvider implements CompletionProvider {
?.getCompletions(context, params) as CompletionItem[];
} else if (
context.entitySection === 'Properties' ||
ResourceAttributesSet.has(context.entitySection as string)
ResourceAttributesSet.has(context.entitySection as string) ||
(context.matchPathWithLogicalId(TopLevelSection.Resources, 'Properties') && context.propertyPath.length > 3)
) {
const schemaPropertyCompletions = this.resourceProviders
.get(ResourceCompletionType.Property)
Expand Down
79 changes: 79 additions & 0 deletions tst/e2e/autocomplete/Autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,47 @@ O`,
};
template.executeScenario(scenario);
});

it('test nested object property completion', () => {
const template = new TemplateBuilder(DocumentType.YAML);
const scenario: TemplateScenario = {
name: 'Nested object property completion',
steps: [
{
action: 'type',
content: `AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
NotificationConfiguration:
`,
position: { line: 0, character: 0 },
description: 'Test autocomplete in nested object within Properties',
verification: {
position: { line: 6, character: 8 },
expectation: CompletionExpectationBuilder.create()
.expectContainsItems([
'TopicConfigurations',
'QueueConfigurations',
'LambdaConfigurations',
'EventBridgeConfiguration',
])
.build(),
},
},
{
action: 'type',
content: `TopicConfigurations:
- Topic: arn:aws:sns:us-east-1:123456789012:my-topic
Event: s3:ObjectCreated:*`,
position: { line: 6, character: 8 },
description: 'Complete the nested object structure',
},
],
};
template.executeScenario(scenario);
});
});

describe('JSON', () => {
Expand Down Expand Up @@ -1479,5 +1520,43 @@ O`,
};
template.executeScenario(scenario);
});

it('test nested object property completion', () => {
const template = new TemplateBuilder(DocumentType.JSON, '');
const scenario: TemplateScenario = {
name: 'Nested object property completion',
steps: [
{
action: 'type',
position: { line: 0, character: 0 },
content: `{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"MyBucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"NotificationConfiguration": {
""
}
}
}
}
}`,
verification: {
position: { line: 7, character: 11 },
expectation: CompletionExpectationBuilder.create()
.expectContainsItems([
'TopicConfigurations',
'QueueConfigurations',
'LambdaConfigurations',
'EventBridgeConfiguration',
])
.build(),
},
},
],
};
template.executeScenario(scenario);
});
});
});
31 changes: 31 additions & 0 deletions tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,37 @@ describe('ResourceSectionCompletionProvider', () => {
spy.mockRestore();
});

test('should delegate to property provider when in nested object within Properties (matchPathWithLogicalIds)', async () => {
const mockContext = createResourceContext('MyBucket', {
text: 'Topic',
propertyPath: ['Resources', 'MyBucket', 'Properties', 'NotificationConfiguration', 'Topic'],
data: {
Type: 'AWS::S3::Bucket',
Properties: {
NotificationConfiguration: {},
},
},
});

const mockSchemas = createMockResourceSchemas();
setupMockSchemas(mockSchemas);

const propertyProvider = resourceProviders.get('Property' as any)!;
const mockCompletions = [
{ label: 'TopicConfigurations', kind: CompletionItemKind.Property },
{ label: 'QueueConfigurations', kind: CompletionItemKind.Property },
{ label: 'LambdaConfigurations', kind: CompletionItemKind.Property },
{ label: 'EventBridgeConfiguration', kind: CompletionItemKind.Property },
];
const spy = vi.spyOn(propertyProvider, 'getCompletions').mockReturnValue(mockCompletions);

const result = await provider.getCompletions(mockContext, mockParams);

expect(spy).toHaveBeenCalledWith(mockContext, mockParams);
expect(result).toEqual(mockCompletions);
spy.mockRestore();
});

test('should return empty array when no provider matches', async () => {
const mockContext = createResourceContext('MyResource', {
text: '',
Expand Down
Loading