@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>
277 lines (252 loc) • 9.8 kB
text/typescript
import {
AbstractFileSystem,
path,
Section,
Setting,
ThemeBlock,
ThemeSchemaType,
} from '@shopify/theme-check-common';
import { MockFileSystem } from '@shopify/theme-check-common/src/test';
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
import { URI, Utils } from 'vscode-uri';
import { DocumentManager } from './DocumentManager';
import { mockConnection } from '../test/MockConnection';
import { ClientCapabilities } from '../ClientCapabilities';
describe('Module: DocumentManager', () => {
const mockRoot = 'mock-fs:';
let documentManager: DocumentManager;
let connection: ReturnType<typeof mockConnection>;
let fs: AbstractFileSystem;
beforeEach(() => {
documentManager = new DocumentManager();
});
it('should return a theme for a root', () => {
// these will be different in windows vs unix
const rootUri = URI.file(__dirname);
const fileUri = Utils.joinPath(rootUri, 'test.liquid');
// We expect forward slash paths (windows path get normalized)
expect(fileUri.path).not.to.include('\\');
documentManager.open(fileUri.toString(), '{{ "hi" }}', 0);
const theme = documentManager.theme(path.normalize(rootUri));
expect(theme).to.have.lengthOf(1);
expect(theme[0].uri).not.to.include('\\');
// `fileURI.toString()` lowercases c: in 'C:\dir\path'
// Without the URI.parse().path, this test was failing for a dumb reason
expect(theme[0].uri).to.equal(path.normalize(fileUri));
});
describe('when initialized with an abstract file system', () => {
beforeEach(async () => {
fs = new MockFileSystem(
{
'snippet/foo.liquid': `hello {% render 'bar' %}`,
'snippet/bar.liquid': `world`,
},
'mock-fs:',
);
documentManager = new DocumentManager(fs);
vi.spyOn(fs, 'readFile');
});
describe('when the abstract file system is preloaded', () => {
beforeEach(async () => {
await documentManager.preload('mock-fs:/');
});
it('preloads source codes with a version of undefined', async () => {
const sc = documentManager.get('mock-fs:/snippet/foo.liquid');
assert(sc);
expect(sc.version).to.equal(undefined);
});
it('returns defined versions of opened files', () => {
documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 0);
const sc = documentManager.get('mock-fs:/snippet/foo.liquid');
assert(sc);
expect(sc.version).to.equal(0);
});
describe('Unit: theme(rootUri, includeFilesFromDisk)', () => {
it('only returns the source codes of the opened files by default', () => {
const theme = documentManager.theme('mock-fs:/');
expect(theme).to.have.lengthOf(0);
});
it('returns all the files when called with includeFilesFromDisk', async () => {
const theme = documentManager.theme('mock-fs:/', true);
expect(theme).to.have.lengthOf(2);
});
});
describe('Unit: close(uri)', () => {
it('sets the source version to undefined (value is on disk)', () => {
documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 10);
documentManager.close('mock-fs:/snippet/foo.liquid');
const sc = documentManager.get('mock-fs:/snippet/foo.liquid');
assert(sc);
expect(sc.source).to.equal('hello {% render "bar" %}');
expect(sc.version).to.equal(undefined);
});
});
describe('Unit: delete(uri)', () => {
it('deletes the source code from the document manager', () => {
// as though the file no longer exists
documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 10);
documentManager.delete('mock-fs:/snippet/foo.liquid');
const sc = documentManager.get('mock-fs:/snippet/foo.liquid');
assert(!sc);
});
});
describe('Unit: preload(rootUri)', () => {
it('should be memoized and only run once', async () => {
await documentManager.preload('mock-fs:/');
await documentManager.preload('mock-fs:/');
await documentManager.preload('mock-fs:/');
await documentManager.preload('mock-fs:/');
expect(vi.mocked(fs.readFile)).toHaveBeenCalledTimes(
documentManager.theme('mock-fs:/', true).length,
);
});
});
});
});
describe('when initialized with a connection & hasProgressSupport', () => {
beforeEach(() => {
const capabilities = new ClientCapabilities();
capabilities.setup({
window: {
workDoneProgress: true,
},
});
connection = mockConnection(mockRoot);
connection.spies.onRequest.mockImplementationOnce(async (method) => {
switch (method) {
case 'window/workDoneProgress/create':
return 'ok';
default:
throw new Error(`Unexpected method: ${method}`);
}
});
fs = new MockFileSystem(
{
'snippet/1.liquid': `hello {% render 'bar' %}`,
'snippet/2.liquid': `hello {% render 'bar' %}`,
'snippet/3.liquid': `hello {% render 'bar' %}`,
'snippet/4.liquid': `hello {% render 'bar' %}`,
'snippet/5.liquid': `hello {% render 'bar' %}`,
'snippet/6.liquid': `hello {% render 'bar' %}`,
'snippet/7.liquid': `hello {% render 'bar' %}`,
'snippet/8.liquid': `hello {% render 'bar' %}`,
'snippet/9.liquid': `hello {% render 'bar' %}`,
'snippet/10.liquid': `hello {% render 'bar' %}`,
},
mockRoot,
);
vi.spyOn(fs, 'readFile');
documentManager = new DocumentManager(fs, connection, capabilities);
});
it('should report progress while preloading', async () => {
await documentManager.preload(mockRoot);
expect(connection.spies.sendProgress).toHaveBeenCalledTimes(4);
expect(connection.spies.sendProgress).toHaveBeenCalledWith(
expect.anything(),
'preload#mock-fs:',
{
kind: 'begin',
title: 'Initializing Liquid LSP',
},
);
expect(connection.spies.sendProgress).toHaveBeenCalledWith(
expect.anything(),
'preload#mock-fs:',
{
kind: 'report',
message: 'Preloading files',
percentage: 10,
},
);
expect(connection.spies.sendProgress).toHaveBeenCalledWith(
expect.anything(),
'preload#mock-fs:',
{
kind: 'report',
message: 'Preloading files [10/10]',
percentage: 100,
},
);
expect(connection.spies.sendProgress).toHaveBeenCalledWith(
expect.anything(),
'preload#mock-fs:',
{
kind: 'end',
message: 'Completed',
},
);
});
});
describe('when initialized with a fs, getModeForURI, and isValidSchema', () => {
const mockFsRoot = path.normalize('mock-fs:/theme');
const sectionSchema: Section.Schema = {
name: 'hello',
settings: [{ type: 'number' as Setting.Type.Number, id: 'my_num' }],
default: { settings: { my_num: 1 } },
};
const blockSchema: ThemeBlock.Schema = {
name: 'world',
blocks: [{ type: '@theme' }],
};
beforeEach(async () => {
fs = new MockFileSystem(
{
'sections/foo.liquid': `
{% schema %}
${JSON.stringify(sectionSchema, null, 2)}
{% endschema %}`,
'blocks/bar.liquid': `
{% schema %}
${JSON.stringify(blockSchema, null, 2)}
{% endschema %}`,
},
mockFsRoot,
);
documentManager = new DocumentManager(
fs,
undefined,
undefined,
async () => 'theme',
async () => true, // let's assume they are valid for testing purposes...
);
await documentManager.preload(mockFsRoot);
});
it('should augment section files with a getSchema property that resolves to a type-safe schema', async () => {
const section = documentManager.get(path.join(mockFsRoot, 'sections/foo.liquid'));
assert(section);
assert(section.type === 'LiquidHtml');
const schema = await section.getSchema();
assert(schema);
assert(schema.type === ThemeSchemaType.Section);
expect(schema.parsed).to.eql(sectionSchema);
const validSchema = schema.validSchema;
assert(!(validSchema instanceof Error));
// Note below that validSchema.name is accessed in a type-safe manner
expect(validSchema.name).to.equal(sectionSchema.name);
assert(validSchema.settings);
for (const setting of validSchema.settings) {
assert(setting.type);
assert(setting.id);
}
// Default is a section-only property
assert(validSchema.default);
});
it('should augment block files with a getSchema property that resolves to a type-safe schema', async () => {
const block = documentManager.get(path.join(mockFsRoot, 'blocks/bar.liquid'));
assert(block);
assert(block.type === 'LiquidHtml');
const schema = await block.getSchema();
assert(schema);
assert(schema.type === ThemeSchemaType.Block);
expect(schema.parsed).to.eql(blockSchema);
const validSchema = schema.validSchema;
assert(!(validSchema instanceof Error));
// Note below that validSchema.name is accessed in a type-safe manner
expect(validSchema.name).to.equal(blockSchema.name);
assert(validSchema.blocks);
for (const block of validSchema.blocks) {
assert(block.type);
}
});
});
});