mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Custom ESLint rules to enforce new ThemedComponent selector convention
The following cases are covered: - ThemedComponent wrapper selectors must not start with ds-themed- - Base component selectors must start with ds-base- - Themed component selectors must start with ds-themed- - The ThemedComponent wrapper must always be used in HTML - The ThemedComponent wrapper must be used in TypeScript _where appropriate_: - Required - Explicit usages (e.g. modal instantiation, routing modules, ...) - By.css selector queries (in order to align with the HTML rule) - Unchecked - Non-routing modules (to ensure the components can be declared) - ViewChild hooks (since they need to attach to the underlying component) All rules work with --fix to automatically migrate to the new convention This covers most of the codebase, but minor manual adjustment are needed afterwards
This commit is contained in:
9
lint/test/fixture/README.md
Normal file
9
lint/test/fixture/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# ESLint testing fixtures
|
||||
|
||||
The files in this directory are used for the ESLint testing environment
|
||||
- Some rules rely on registries that must be built up _before_ the rule is run
|
||||
- In order to test these registries, the fixture sources contain a few dummy components
|
||||
- The TypeScript ESLint test runner requires at least one dummy file to exist to run any tests
|
||||
- By default, [`test.ts`](./src/test.ts) is used. Note that this file is empty; it's only there for the TypeScript configuration, the actual content is injected from the `code` property in the tests.
|
||||
- To test rules that make assertions based on the path of the file, you'll need to include the `filename` property in the test configuration. Note that it must point to an existing file too!
|
||||
- The `filename` must be provided as `fixture('src/something.ts')`
|
14
lint/test/fixture/src/app/test/test-routing.module.ts
Normal file
14
lint/test/fixture/src/app/test/test-routing.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { ThemedTestThemeableComponent } from './themed-test-themeable.component';
|
||||
|
||||
export const ROUTES = [
|
||||
{
|
||||
component: ThemedTestThemeableComponent,
|
||||
}
|
||||
];
|
15
lint/test/fixture/src/app/test/test-themeable.component.ts
Normal file
15
lint/test/fixture/src/app/test/test-themeable.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-base-test-themeable',
|
||||
template: '',
|
||||
})
|
||||
export class TestThemeableComponent {
|
||||
}
|
8
lint/test/fixture/src/app/test/test.component.spec.ts
Normal file
8
lint/test/fixture/src/app/test/test.component.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 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/
|
||||
*/
|
||||
|
15
lint/test/fixture/src/app/test/test.component.ts
Normal file
15
lint/test/fixture/src/app/test/test.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-test',
|
||||
template: '',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
23
lint/test/fixture/src/app/test/test.module.ts
Normal file
23
lint/test/fixture/src/app/test/test.module.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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/
|
||||
*/
|
||||
// @ts-ignore
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TestThemeableComponent } from './test-themeable.component';
|
||||
import { TestComponent } from './test.component';
|
||||
import { ThemedTestThemeableComponent } from './themed-test-themeable.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TestComponent,
|
||||
TestThemeableComponent,
|
||||
ThemedTestThemeableComponent,
|
||||
]
|
||||
})
|
||||
export class TestModule {
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 { Component } from '@angular/core';
|
||||
import { ThemedComponent } from '../../../../../../src/app/shared/theme-support/themed.component';
|
||||
import { TestThemeableComponent } from './test-themeable.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-test-themeable',
|
||||
template: '',
|
||||
})
|
||||
export class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||
protected getComponentName(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
protected importThemedComponent(themeName: string): Promise<any> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
protected importUnthemedComponent(): Promise<any> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
0
lint/test/fixture/src/test.ts
Normal file
0
lint/test/fixture/src/test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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 { Component } from '@angular/core';
|
||||
import { TestThemeableComponent as BaseComponent } from '../../../../app/test/test-themeable.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-themed-test-themeable',
|
||||
template: '',
|
||||
})
|
||||
export class TestThemeableComponent extends BaseComponent {
|
||||
|
||||
}
|
19
lint/test/fixture/src/themes/test/test.module.ts
Normal file
19
lint/test/fixture/src/themes/test/test.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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/
|
||||
*/
|
||||
// @ts-ignore
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TestThemeableComponent } from './app/test/test-themeable.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TestThemeableComponent,
|
||||
]
|
||||
})
|
||||
export class TestModule {
|
||||
|
||||
}
|
7
lint/test/fixture/tsconfig.json
Normal file
7
lint/test/fixture/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": ["dist"]
|
||||
}
|
13
lint/test/helpers.js
Normal file
13
lint/test/helpers.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||
const StacktraceOption = require('jasmine-spec-reporter').StacktraceOption;
|
||||
|
||||
jasmine.getEnv().clearReporters(); // Clear default console reporter for those instead
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayErrorMessages: false,
|
||||
},
|
||||
summary: {
|
||||
displayFailed: true,
|
||||
displayStacktrace: StacktraceOption.PRETTY,
|
||||
},
|
||||
}));
|
140
lint/test/rules/themed-component-selectors.spec.ts
Normal file
140
lint/test/rules/themed-component-selectors.spec.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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 {
|
||||
fixture,
|
||||
tsRuleTester,
|
||||
} from '../testing';
|
||||
import rule from '../../src/rules/ts/themed-component-selectors';
|
||||
|
||||
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);
|
||||
});
|
190
lint/test/rules/themed-component-usages.spec.ts
Normal file
190
lint/test/rules/themed-component-usages.spec.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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 {
|
||||
fixture,
|
||||
htmlRuleTester,
|
||||
tsRuleTester,
|
||||
} from '../testing';
|
||||
import tsRule from '../../src/rules/ts/themed-component-usages';
|
||||
import htmlRule from '../../src/rules/html/themed-component-usages';
|
||||
|
||||
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');
|
||||
`,
|
||||
},
|
||||
],
|
||||
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');
|
||||
`,
|
||||
},
|
||||
],
|
||||
} 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>
|
||||
`,
|
||||
},
|
||||
],
|
||||
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>
|
||||
`,
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
52
lint/test/testing.ts
Normal file
52
lint/test/testing.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 { RuleTester } from 'eslint';
|
||||
import { RuleTester as TypeScriptRuleTester } from '@typescript-eslint/rule-tester';
|
||||
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: {
|
||||
ts: fixture('src/test.ts'),
|
||||
tsx: 'n/a',
|
||||
},
|
||||
parserOptions: {
|
||||
project: fixture('tsconfig.json'),
|
||||
}
|
||||
});
|
||||
|
||||
class HtmlRuleTester extends RuleTester {
|
||||
run(name: string, rule: any, tests: { valid: any[], invalid: any[] }) {
|
||||
super.run(name, rule, {
|
||||
valid: tests.valid.map((test) => ({
|
||||
filename: fixture('test.html'),
|
||||
...test,
|
||||
})),
|
||||
invalid: tests.invalid.map((test) => ({
|
||||
filename: fixture('test.html'),
|
||||
...test,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const htmlRuleTester = new HtmlRuleTester({
|
||||
parser: require.resolve('@angular-eslint/template-parser'),
|
||||
});
|
24
lint/test/util/theme-support.spec.ts
Normal file
24
lint/test/util/theme-support.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 { themeableComponents } from '../../src/util/theme-support';
|
||||
|
||||
describe('theme-support', () => {
|
||||
describe('themeable component registry', () => {
|
||||
it('should contain all themeable components from the fixture', () => {
|
||||
expect(themeableComponents.entries.size).toBe(1);
|
||||
expect(themeableComponents.byBasePath.size).toBe(1);
|
||||
expect(themeableComponents.byWrapperPath.size).toBe(1);
|
||||
expect(themeableComponents.byBaseClass.size).toBe(1);
|
||||
|
||||
expect(themeableComponents.byBaseClass.get('TestThemeableComponent')).toBeTruthy();
|
||||
expect(themeableComponents.byBasePath.get('src/app/test/test-themeable.component.ts')).toBeTruthy();
|
||||
expect(themeableComponents.byWrapperPath.get('src/app/test/themed-test-themeable.component.ts')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user