@shopify/theme-language-server-common
Version:
<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>
273 lines (247 loc) • 8.45 kB
text/typescript
import { describe, beforeEach, it, expect, vi } from 'vitest';
import { DocumentManager } from '../../documents';
import { CompletionsProvider } from '../CompletionsProvider';
import { SettingsSchemaJSONFile } from '../../settings';
describe('Module: ObjectAttributeCompletionProvider', async () => {
let provider: CompletionsProvider;
let settingsProvider: any;
beforeEach(async () => {
settingsProvider = vi.fn().mockResolvedValue([]);
provider = new CompletionsProvider({
documentManager: new DocumentManager(),
themeDocset: {
filters: async () => [
{ name: 'split', return_type: [{ type: 'array', array_value: 'string' }] },
{ name: 'upcase', return_type: [{ type: 'string', name: '' }] },
{ name: 'downcase', return_type: [{ type: 'string', name: '' }] },
],
objects: async () => [
{
name: 'settings',
properties: [],
},
{
name: 'global_default',
properties: [{ name: 'prop1' }, { name: 'prop2' }],
},
{
name: 'global_access',
access: {
global: true,
parents: [],
template: [],
},
properties: [{ name: 'prop3' }, { name: 'prop4' }],
},
{
name: 'product',
access: {
global: false,
parents: [],
template: ['product'],
},
properties: [
{
name: 'images',
return_type: [
{
type: 'array',
array_value: 'image',
},
],
},
],
},
{
name: 'image',
access: {
global: false, // image is a type, but not a global variable
parents: [],
template: [],
},
properties: [
{ name: 'src', return_type: [{ type: 'string', name: '' }] },
{ name: 'width', return_type: [{ type: 'number', name: '' }] },
{ name: 'height', return_type: [{ type: 'number', name: '' }] },
],
},
],
tags: async () => [],
systemTranslations: async () => ({}),
},
getThemeSettingsSchemaForURI: settingsProvider,
});
});
it('does not complete number lookups', async () => {
await expect(provider).to.complete('{{ product[01█ }}', []);
});
it('does not complete boolean lookups', async () => {
await expect(provider).to.complete('{{ product[tr█ }}', []);
});
it('does complete string lookups', async () => {
await expect(provider).to.complete('{{ product["█ }}', ['images']);
});
it('has nothing to complete for numbers', async () => {
const sources = [
`{% assign x = 10 %}
{{ x.█ }}`,
`{% for x in (0..5) %}
{{ x.█ }}
{% endfor %}`,
];
for (const source of sources) {
await expect(provider).to.complete(source, []);
}
});
describe('Case: global variables', () => {
it('returns the properties of global variables', async () => {
await expect(provider).to.complete('{{ global_default.█ }}', ['prop1', 'prop2']);
await expect(provider).to.complete('{{ global_access.█ }}', ['prop3', 'prop4']);
});
it('returns the properties of non-global variables that could be global per template', async () => {
await expect(provider).to.complete('{{ product.█ }}', ['images']);
});
it('does not complete the properties of a non-global type', async () => {
await expect(provider).to.complete('{{ image.█ }}', []);
});
});
describe('Case: scoping and inference', () => {
it('returns the properties of a resolved variable', async () => {
const source = `
{% assign x = global_default %}
{{ x.p█ }}
`;
await expect(provider).to.complete(source, ['prop1', 'prop2']);
});
it('returns the properties of the infered type of a deep lookup', async () => {
const source = `
{% assign x = product.images.first %}
{{ x.s█ }}
`;
await expect(provider).to.complete(source, ['src']);
});
it('returns the properties of the infered type of a series of threaded types', async () => {
const source = `
{% assign x = product %}
{% assign y = x.images %}
{% assign z = y.first %}
{{ z.s█ }}
`;
await expect(provider).to.complete(source, ['src']);
});
it('returns the properties of the infered type of a series of threaded types (liquid tag)', async () => {
const source = `
{% liquid
assign x = product
assign y = x.images
assign z = y.first
echo z.s█
%}
`;
await expect(provider).to.complete(source, ['src']);
});
describe('When: inside a for/tablerow loop', () => {
it('returns the properties of the array_value of the array', async () => {
for (const tag of ['for', 'tablerow']) {
const source = `
{% # x is global_default %}
{% assign x = global_default %}
{% # x is image only in for loop %}
{% ${tag} x in product.images %}
{{ x.s█ }}
{% end${tag} %}
`;
await expect(provider, source).to.complete(source, ['src']);
}
});
});
it('returns the properties of the last known type of a thing', async () => {
const source = `
{% # x is global_default %}
{% assign x = global_default %}
{% # x is image only in for loop %}
{% for x in product.images %}
{{ x.src }}
{% endfor %}
{% # x is still global_default %}
{{ x.█ }}
`;
await expect(provider).to.complete(source, ['prop1', 'prop2']);
});
});
describe('Case: capture', () => {
it('returns the properties of a captured string', async () => {
const source = `
{% capture x %}
...
{% endcapture %}
{{ x.█ }}
`;
await expect(provider).to.complete(source, ['size']);
});
});
describe('Case: array parent type', () => {
it('returns the properties of a created array from filter', async () => {
const source = `
{% assign x = '123' | split: '' %}
{{ x.█ }}
`;
await expect(provider).to.complete(source, ['first', 'last', 'size']);
});
it('returns the properties of the array_value', async () => {
const sources = [
`{% assign x = product.images %}
{{ x.first.█ }}`,
`{% assign x = product.images %}
{{ x.last.█ }}`,
`{% assign x = product.images %}
{{ x[0].█ }}`,
`{% assign x = product.images %}
{{ x[1].█ }}`,
`{% assign x = product.images %}
{% assign lookup = 0 %}
{{ x[lookup].█ }}`,
];
for (const source of sources) {
await expect(provider, source).to.complete(source, ['height', 'src', 'width']);
}
});
});
describe('Case: infered filter return type', () => {
it('should return the properties of a string return type', async () => {
const source = `
{% assign x = product.images.first.src | upcase | downcase %}
{{ x.█ }}
`;
await expect(provider).to.complete(source, ['size']);
});
it('should return the properties of an array return type', async () => {
const source = `
{% assign x = product.images.first.src | split: ',' %}
{{ x.█ }}
`;
await expect(provider).to.complete(source, ['first', 'last', 'size']);
});
});
describe('Case: global settings', () => {
it('should complete basic settings by id as expected', async () => {
const settings: SettingsSchemaJSONFile = [
{
name: 'Category',
settings: [
{
type: 'product',
id: 'my_product_setting',
label: 'Product',
},
],
},
];
settingsProvider.mockResolvedValue(settings);
const source = `
{{ settings.█ }}
`;
await expect(provider).to.complete(source, ['my_product_setting']);
});
});
});