Enforce plugin structure and generate documentation

This commit is contained in:
Yury Bondarenko
2024-03-14 18:14:30 +01:00
parent e83a0cd741
commit b0758c23e5
19 changed files with 833 additions and 446 deletions

View File

@@ -0,0 +1,13 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
export const FIXTURE = 'lint/test/fixture/';
export function fixture(path: string): string {
return FIXTURE + path;
}

26
lint/test/rules.spec.ts Normal file
View File

@@ -0,0 +1,26 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { default as htmlPlugin } from '../src/rules/html';
import { default as tsPlugin } from '../src/rules/ts';
import {
htmlRuleTester,
tsRuleTester,
} from './testing';
describe('TypeScript rules', () => {
for (const { info, rule, tests } of tsPlugin.index) {
tsRuleTester.run(info.name, rule, tests);
}
});
describe('HTML rules', () => {
for (const { info, rule, tests } of htmlPlugin.index) {
htmlRuleTester.run(info.name, rule, tests);
}
});

View File

@@ -1,140 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import rule from '../../src/rules/ts/themed-component-selectors';
import {
fixture,
tsRuleTester,
} from '../testing';
describe('themed-component-selectors', () => {
tsRuleTester.run('themed-component-selectors', rule as any, {
valid: [
{
name: 'Regular non-themeable component selector',
code: `
@Component({
selector: 'ds-something',
})
class Something {
}
`,
},
{
name: 'Themeable component selector should replace the original version, unthemed version should be changed to ds-base-',
code: `
@Component({
selector: 'ds-base-something',
})
class Something {
}
@Component({
selector: 'ds-something',
})
class ThemedSomething extends ThemedComponent<Something> {
}
@Component({
selector: 'ds-themed-something',
})
class OverrideSomething extends Something {
}
`,
},
{
name: 'Other themed component wrappers should not interfere',
code: `
@Component({
selector: 'ds-something',
})
class Something {
}
@Component({
selector: 'ds-something-else',
})
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
}
`,
},
],
invalid: [
{
name: 'Wrong selector for base component',
filename: fixture('src/app/test/test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-something',
})
class TestThemeableComponent {
}
`,
errors: [
{
messageId: 'wrongSelectorUnthemedComponent',
},
],
output: `
@Component({
selector: 'ds-base-something',
})
class TestThemeableComponent {
}
`,
},
{
name: 'Wrong selector for wrapper component',
filename: fixture('src/app/test/themed-test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-themed-something',
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
errors: [
{
messageId: 'wrongSelectorThemedComponentWrapper',
},
],
output: `
@Component({
selector: 'ds-something',
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
},
{
name: 'Wrong selector for theme override',
filename: fixture('src/themes/test/app/test/test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-something',
})
class TestThememeableComponent extends BaseComponent {
}
`,
errors: [
{
messageId: 'wrongSelectorThemedComponentOverride',
},
],
output: `
@Component({
selector: 'ds-themed-something',
})
class TestThememeableComponent extends BaseComponent {
}
`,
},
],
} as any);
});

View File

@@ -1,265 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import htmlRule from '../../src/rules/html/themed-component-usages';
import tsRule from '../../src/rules/ts/themed-component-usages';
import {
fixture,
htmlRuleTester,
tsRuleTester,
} from '../testing';
describe('themed-component-usages (TypeScript)', () => {
tsRuleTester.run('themed-component-usages', tsRule as any, {
valid: [
{
code: `
const config = {
a: ThemedTestThemeableComponent,
b: ChipsComponent,
}
`,
},
{
code: `
export class TestThemeableComponent {
}
`,
},
{
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
},
{
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
export class Something {
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
}
`,
},
{
name: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-themeable');
By.Css('#test > ds-themeable > #nest');
`,
},
{
name: fixture('src/app/test/test.component.cy.ts'),
code: `
By.css('ds-themeable');
By.Css('#test > ds-themeable > #nest');
`,
},
],
invalid: [
{
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
import { TestComponent } from '../test/test.component.ts';
const config = {
a: TestThemeableComponent,
b: TestComponent,
}
`,
errors: [
{
messageId: 'mustImportThemedWrapper',
},
{
messageId: 'mustUseThemedWrapper',
},
],
output: `
import { ThemedTestThemeableComponent } from '../test/themed-test-themeable.component.ts';
import { TestComponent } from '../test/test.component.ts';
const config = {
a: ThemedTestThemeableComponent,
b: TestComponent,
}
`,
},
{
filename: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-themed-themeable');
By.css('#test > ds-themed-themeable > #nest');
`,
errors: [
{
messageId: 'mustUseThemedWrapper',
},
{
messageId: 'mustUseThemedWrapper',
},
],
output: `
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
`,
},
{
filename: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-base-themeable');
By.css('#test > ds-base-themeable > #nest');
`,
errors: [
{
messageId: 'mustUseThemedWrapper',
},
{
messageId: 'mustUseThemedWrapper',
},
],
output: `
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
`,
},
{
filename: fixture('src/app/test/test.component.cy.ts'),
code: `
cy.get('ds-themed-themeable');
cy.get('#test > ds-themed-themeable > #nest');
`,
errors: [
{
messageId: 'mustUseThemedWrapper',
},
{
messageId: 'mustUseThemedWrapper',
},
],
output: `
cy.get('ds-themeable');
cy.get('#test > ds-themeable > #nest');
`,
},
{
filename: fixture('src/app/test/test.component.cy.ts'),
code: `
cy.get('ds-base-themeable');
cy.get('#test > ds-base-themeable > #nest');
`,
errors: [
{
messageId: 'mustUseThemedWrapper',
},
{
messageId: 'mustUseThemedWrapper',
},
],
output: `
cy.get('ds-themeable');
cy.get('#test > ds-themeable > #nest');
`,
},
],
} as any);
});
describe('themed-component-usages (HTML)', () => {
htmlRuleTester.run('themed-component-usages', htmlRule, {
valid: [
{
code: `
<ds-test-themeable/>
<ds-test-themeable></ds-test-themeable>
<ds-test-themeable [test]="something"></ds-test-themeable>
`,
},
{
name: fixture('src/test.ts'),
code: `
@Component({
template: '<ds-test-themeable></ds-test-themeable>'
})
class Test {
}
`,
},
{
name: fixture('src/test.spec.ts'),
code: `
@Component({
template: '<ds-test-themeable></ds-test-themeable>'
})
class Test {
}
`,
},
{
filename: fixture('src/test.spec.ts'),
code: `
@Component({
template: '<ds-base-test-themeable></ds-base-test-themeable>'
})
class Test {
}
`,
},
],
invalid: [
{
code: `
<ds-themed-test-themeable/>
<ds-themed-test-themeable></ds-themed-test-themeable>
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
`,
errors: [
{
messageId: 'mustUseThemedWrapperSelector',
},
{
messageId: 'mustUseThemedWrapperSelector',
},
{
messageId: 'mustUseThemedWrapperSelector',
},
],
output: `
<ds-test-themeable/>
<ds-test-themeable></ds-test-themeable>
<ds-test-themeable [test]="something"></ds-test-themeable>
`,
},
{
code: `
<ds-base-test-themeable/>
<ds-base-test-themeable></ds-base-test-themeable>
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
`,
errors: [
{
messageId: 'mustUseThemedWrapperSelector',
},
{
messageId: 'mustUseThemedWrapperSelector',
},
{
messageId: 'mustUseThemedWrapperSelector',
},
],
output: `
<ds-test-themeable/>
<ds-test-themeable></ds-test-themeable>
<ds-test-themeable [test]="something"></ds-test-themeable>
`,
},
],
});
});

View File

@@ -0,0 +1,76 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
import { default as html } from '../src/rules/html';
import { default as ts } from '../src/rules/ts';
describe('plugin structure', () => {
for (const pluginExports of [ts, html]) {
const pluginName = pluginExports.name ?? 'UNNAMED PLUGIN';
describe(pluginName, () => {
it('should have a name', () => {
expect(pluginExports.name).toBeTruthy();
});
it('should have rules', () => {
expect(pluginExports.index).toBeTruthy();
expect(pluginExports.rules).toBeTruthy();
expect(pluginExports.index.length).toBeGreaterThan(0);
});
for (const ruleExports of pluginExports.index) {
const ruleName = ruleExports.info.name ?? 'UNNAMED RULE';
describe(ruleName, () => {
it('should have a name', () => {
expect(ruleExports.info.name).toBeTruthy();
});
it('should be included under the right name in the plugin', () => {
expect(pluginExports.rules[ruleExports.info.name]).toBe(ruleExports.rule);
});
it('should contain metadata', () => {
expect(ruleExports.info).toBeTruthy();
expect(ruleExports.info.name).toBeTruthy();
expect(ruleExports.info.meta).toBeTruthy();
expect(ruleExports.info.defaultOptions).toBeTruthy();
});
it('should contain messages', () => {
expect(ruleExports.Message).toBeTruthy();
expect(ruleExports.info.meta.messages).toBeTruthy();
});
describe('messages', () => {
for (const member of Object.keys(ruleExports.Message)) {
describe(member, () => {
const id = (ruleExports.Message as any)[member];
it('should have a valid ID', () => {
expect(id).toBeTruthy();
});
it('should have valid metadata', () => {
expect(ruleExports.info.meta.messages[id]).toBeTruthy();
});
});
}
});
it('should contain tests', () => {
expect(ruleExports.tests).toBeTruthy();
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
expect(ruleExports.tests.invalid.length).toBeGreaterThan(0);
});
});
}
});
}
});

View File

@@ -8,20 +8,19 @@
import { RuleTester as TypeScriptRuleTester } from '@typescript-eslint/rule-tester';
import { RuleTester } from 'eslint';
import {
FIXTURE,
fixture,
} from './fixture';
import { themeableComponents } from '../src/util/theme-support';
const FIXTURE = 'lint/test/fixture/';
// Register themed components from test fixture
themeableComponents.initialize(FIXTURE);
TypeScriptRuleTester.itOnly = fit;
export function fixture(path: string): string {
return FIXTURE + path;
}
export const tsRuleTester = new TypeScriptRuleTester({
parser: '@typescript-eslint/parser',
defaultFilenames: {

View File

@@ -6,7 +6,7 @@
* http://www.dspace.org/license/
*/
import { themeableComponents } from '../../src/util/theme-support';
import { themeableComponents } from '../src/util/theme-support';
describe('theme-support', () => {
describe('themeable component registry', () => {