@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>
331 lines (289 loc) • 10.7 kB
text/typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { URI } from 'vscode-uri';
import { Offense, SourceCodeType, Severity, path } from '@shopify/theme-check-common';
import { DiagnosticsManager } from '../../diagnostics';
import { DocumentManager } from '../../documents';
import { FixProvider } from './FixProvider';
import { TextDocument } from 'vscode-languageserver-textdocument';
describe('Unit: FixProvider', () => {
const uri = path.normalize(URI.file('/path/to/file.liquid'));
const contents = `
{% assign x = 1 %}
<script src="2.js"></script>
<script src="3.js"></script>
`;
const document = TextDocument.create(uri, 'liquid', 0, contents);
let documentManager: DocumentManager;
let diagnosticsManager: DiagnosticsManager;
let fixProvider: FixProvider;
function makeOffense(
checkName: string,
needle: string,
unfixable?: boolean,
): Offense<SourceCodeType.LiquidHtml> {
const start = contents.indexOf(needle);
const end = start + needle.length;
return {
type: SourceCodeType.LiquidHtml,
check: checkName,
message: 'Parser blocking script detected',
uri: 'file:///path/to/file.liquid',
severity: Severity.ERROR,
start: { ...document.positionAt(start), index: start },
end: { ...document.positionAt(end), index: end },
fix: unfixable ? undefined : (corrector) => corrector.replace(18, 36, 'fixed'),
};
}
beforeEach(() => {
documentManager = new DocumentManager();
diagnosticsManager = new DiagnosticsManager({ sendDiagnostics: vi.fn() } as any);
documentManager.open(uri, contents, 1);
fixProvider = new FixProvider(documentManager, diagnosticsManager);
});
describe('When multiple offenses of various types exist, and the selection includes an offense with at least one more of the same type in the file', () => {
beforeEach(() => {
diagnosticsManager.set(uri, 1, [
makeOffense('UnusedAssign', '{% assign x = 1 %}'),
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
makeOffense('ParserBlockingScript', '<script src="3.js"></script>'),
]);
});
it('provides all code actions categories', async () => {
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(3);
const [fixCursorAction, fixSameTypeAction, fixAllAction] = codeActions;
expect(fixCursorAction).toEqual({
title: 'Fix this ParserBlockingScript problem: Parser blocking script detected',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: true,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [1]],
},
});
expect(fixSameTypeAction).toEqual({
title: 'Fix all ParserBlockingScript problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [1, 2]],
},
});
expect(fixAllAction).toEqual({
title: 'Fix all auto-fixable problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0, 1, 2]],
},
});
});
});
describe('When multiple offenses of various types exist, and the selection includes an offense without more of the same type in the file', () => {
beforeEach(() => {
diagnosticsManager.set(uri, 1, [
makeOffense('UnusedAssign', '{% assign x = 1 %}'),
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
]);
});
it('provides code actions, and the fix all same type is omitted', async () => {
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(2);
const [fixCursorAction, fixAllAction] = codeActions;
expect(fixCursorAction).toEqual({
title: 'Fix this ParserBlockingScript problem: Parser blocking script detected',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: true,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [1]],
},
});
expect(fixAllAction).toEqual({
title: 'Fix all auto-fixable problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0, 1]],
},
});
});
});
describe('When only one offense type exists, and the selection includes an offense', () => {
beforeEach(() => {
diagnosticsManager.set(uri, 1, [
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
makeOffense('ParserBlockingScript', '<script src="3.js"></script>'),
]);
});
it('provides code actions, and the fix all code action is omitted', async () => {
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(2);
const [fixCursorAction, fixSameTypeAction] = codeActions;
expect(fixCursorAction).toEqual({
title: 'Fix this ParserBlockingScript problem: Parser blocking script detected',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: true,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0]],
},
});
expect(fixSameTypeAction).toEqual({
title: 'Fix all ParserBlockingScript problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0, 1]],
},
});
});
});
describe('When there is only one offense', () => {
beforeEach(() => {
diagnosticsManager.set(uri, 1, [
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
]);
});
it('provides a code action to fix it', async () => {
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(1);
const [fixCursorAction] = codeActions;
expect(fixCursorAction).toEqual({
title: 'Fix this ParserBlockingScript problem: Parser blocking script detected',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: true,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0]],
},
});
});
});
it('provides code actions for a set of fixable offenses', async () => {
diagnosticsManager.set(uri, 1, [
makeOffense('UnusedAssign', '{% assign x = 1 %}'),
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
makeOffense('ParserBlockingScript', '<script src="3.js"></script>'),
]);
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(3);
const [fixCursorAction, fixSameTypeAction, fixAllAction] = codeActions;
expect(fixCursorAction).toEqual({
title: 'Fix this ParserBlockingScript problem: Parser blocking script detected',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: true,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [1]],
},
});
expect(fixSameTypeAction).toEqual({
title: 'Fix all ParserBlockingScript problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [1, 2]],
},
});
expect(fixAllAction).toEqual({
title: 'Fix all auto-fixable problems',
kind: 'quickfix',
diagnostics: expect.any(Array),
isPreferred: false,
command: {
title: 'applyFixes',
command: 'themeCheck/applyFixes',
arguments: [uri, 1, [0, 1, 2]],
},
});
});
it('does not provide code actions for non-fixable offenses', async () => {
const nonFixableOffense = makeOffense('ParserBlockingScript', '<script src="2.js">', true);
diagnosticsManager.set(uri, 1, [nonFixableOffense]);
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('2.js')),
end: document.positionAt(contents.indexOf('2.js')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(0);
});
it('does not provide code actions if selection range does not overlap with the error', async () => {
diagnosticsManager.set(uri, 1, [
makeOffense('ParserBlockingScript', '<script src="2.js"></script>'),
makeOffense('ParserBlockingScript', '<script src="3.js"></script>'),
]);
const codeActions = fixProvider.codeActions({
textDocument: { uri },
range: {
start: document.positionAt(contents.indexOf('assign x = 1')),
end: document.positionAt(contents.indexOf('= 1')),
},
context: { diagnostics: [] },
});
expect(codeActions.length).toBe(0);
});
});