Skip to content

Commit

Permalink
Meta: Move feature checker to Vitest (refined-github#7145)
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante authored Dec 11, 2023
1 parent f5884e8 commit d44e947
Show file tree
Hide file tree
Showing 13 changed files with 1,156 additions and 1,062 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
node-version-file: package.json
cache: npm
- run: npm ci
- run: npm run test:features
- run: npm run vitest -- --testNamePattern features.test

TestURLs:
runs-on: ubuntu-latest
Expand All @@ -34,7 +34,7 @@ jobs:
id: changed-files
uses: tj-actions/changed-files@v35
with:
files: source/features/*.tsx
files: source/features/*.{tsx,css}

- name: Every new/edited file must have a test URL
run: bash build/verify-test-urls.sh ${{steps.changed-files.outputs.all_changed_files}}
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
node-version-file: package.json
cache: npm
- run: npm ci
- run: npm run vitest
- run: npm run vitest -- --testNamePattern !features.test

Lint:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .xo-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"no-alert": "off",
"no-warning-comments": "off",
"unicorn/no-nested-ternary": "off",
"unicorn/better-regex": "off",
"unicorn/prefer-top-level-await": "off",
"unicorn/prefer-dom-node-dataset": "off",
"unicorn/expiring-todo-comments": ["warn", {
Expand Down
1,864 changes: 941 additions & 923 deletions build/__snapshots__/features-meta.json

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions build/features.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {test, describe, assert} from 'vitest';
import {parse, join} from 'node:path';
import {existsSync, readdirSync, readFileSync} from 'node:fs';
import regexJoin from 'regex-join';
import fastIgnore from 'fast-ignore';

import {isFeaturePrivate} from '../source/helpers/feature-utils.js';
import {getImportedFeatures, getFeaturesMeta} from './readme-parser.js';

const isGitIgnored = fastIgnore(readFileSync('.gitignore', 'utf8'));

const noScreenshotExceptions = new Set([
// Only add feature here if it's a shortcut only and/or extremely clear by name or description
'sort-conversations-by-update-time',
'prevent-pr-merge-panel-opening',
'infinite-scroll',
'command-palette-navigation-shortcuts',
'comment-fields-keyboard-shortcuts',
'copy-on-y',
'create-release-shortcut',
'pagination-hotkey',
'profile-hotkey',
'repo-wide-file-finder',
'select-all-notifications-shortcut',
'selection-in-new-tab',
'submission-via-ctrl-enter-everywhere',

'hide-navigation-hover-highlight', // TODO: Add side-by-side gif
'hide-inactive-deployments', // TODO: side-by-side png
'esc-to-deselect-line', // TODO Add gif with key overlay
'hide-newsfeed-noise', // TODO: Add side-by-side png
'scrollable-areas', // TODO: Add side-by-side png
]);

const entryPoint = 'source/refined-github.ts';
const entryPointSource = readFileSync(entryPoint);
const importedFeatures = getImportedFeatures();
const featuresInReadme = getFeaturesMeta();

// We used to enforce the filetype, but this is no longer possible with new URLs
// https://github.com/refined-github/refined-github/pull/7130
const imageRegex = /\.(png|gif)$/;

const rghUploadsRegex = /refined-github[/]refined-github[/]assets[/]/;

const screenshotRegex = regexJoin(imageRegex, /|/, rghUploadsRegex);

class FeatureFile {
readonly id: FeatureID;
readonly path: string;
constructor(readonly name: string) {
this.id = parse(name).name as FeatureID;
this.path = join('source/features', name);
}

exists(): boolean {
return existsSync(this.path);
}

// eslint-disable-next-line n/prefer-global/buffer -- Type only
contents(): Buffer {
return readFileSync(this.path);
}

get tsx(): FeatureFile {
if (this.name.endsWith('.gql')) {
const id = importedFeatures.find(featureId => this.id.startsWith(featureId));
if (id) {
return new FeatureFile(id + '.tsx');
}
}

return new FeatureFile(this.id + '.tsx');
}

get css(): FeatureFile {
return new FeatureFile(this.id + '.css');
}
}

function validateCss(file: FeatureFile): void {
const isImportedByEntrypoint = entryPointSource.includes(`import './features/${file.name}';`);
if (!file.tsx.exists()) {
assert(
isImportedByEntrypoint,
`Should be imported by \`${entryPoint}\` or removed if it is not needed`,
);
return;
}

assert(
file.tsx.contents().includes(`import './${file.name}';`),
`Should be imported by \`${file.tsx.name}\``,
);

assert(
!isImportedByEntrypoint,
`Should only be imported by \`${file.tsx.name}\`, not by \`${entryPoint}\``,
);
}

function validateGql(file: FeatureFile): void {
assert(
file.tsx.exists(),
'Does not match any existing features. The filename should match the feature that uses it.',
);

assert(
file.tsx.contents().includes(`from './${file.name}';`),
`Should be imported by \`${file.tsx.name}\``,
);
}

function validateReadme(featureId: FeatureID): void {
const [featureMeta, duplicate] = featuresInReadme.filter(feature => feature.id === featureId);
assert(featureMeta, 'Should be described in the readme');

assert(
featureMeta.description.length >= 20,
'Should be described better in the readme (at least 20 characters)',
);

assert(
screenshotRegex.test(featureMeta.screenshot!)
|| noScreenshotExceptions.has(featureId),
'Should have a screenshot (png/gif) in the readme, unless really difficult to demonstrate (to be discussed in review)',
);

assert(!duplicate, 'Should be described only once in the readme');
}

function validateTsx(file: FeatureFile): void {
assert(
importedFeatures.includes(file.id),
`Should be imported by \`${entryPoint}\``,
);

const fileContents = readFileSync(`source/features/${file.name}`);

if (fileContents.includes('.addCssFeature')) {
assert(
!fileContents.includes('.add('),
`${file.id} should use either \`addCssFeature\` or \`add\`, not both`,
);

assert(
file.css.exists(),
`${file.id} uses \`.addCssFeature\`, but ${file.css.name} is missing`,
);

assert(
file.css.contents().includes(`[rgh-${file.id}]`),
`${file.css.name} should contain a \`[rgh-${file.id}]\` selector`,
);
}

if (!isFeaturePrivate(file.name)) {
validateReadme(file.id);
}
}

describe('features', async () => {
const featuresDirContents = readdirSync('source/features/');
test.each(featuresDirContents)('%s', filename => {
if (isGitIgnored(filename)) {
return;
}

if (filename.endsWith('.gql')) {
validateGql(new FeatureFile(filename));
return;
}

if (filename.endsWith('.css')) {
validateCss(new FeatureFile(filename));
return;
}

if (filename.endsWith('.tsx')) {
validateTsx(new FeatureFile(filename));
return;
}

assert.fail(`The \`/source/features\` folder should only contain .css, .tsx and .gql files. Found \`source/features/${filename}\``);
});
});
7 changes: 6 additions & 1 deletion build/readme-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
test('readme-parser', async () => {
await expect(getImportedFeatures().join('\n') + '\n')
.toMatchFileSnapshot('./__snapshots__/imported-features.txt');
await expect(JSON.parse(JSON.stringify(getFeaturesMeta())))
const featuresMetaJson = JSON.stringify(
getFeaturesMeta(),
(_, value) => value ?? null, // Convert undefined to null to make them visible in the JSON
'\t',
);
await expect(featuresMetaJson + '\n')
.toMatchFileSnapshot('./__snapshots__/features-meta.json');
});
3 changes: 0 additions & 3 deletions build/readme-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ import parseMarkdown from 'snarkdown';
// Group names must be unique because they will be merged
const simpleFeatureRegex = /^- \[]\(# "(?<simpleId>[^"]+)"\)(?: 🔥)? (?<simpleDescription>.+)$/gm;
const highlightedFeatureRegex = /<p><a title="(?<highlightedId>[^"]+)"><\/a> (?<highlightedDescripion>.+?)\n\t+<p><img src="(?<highlightedImage>.+?)">/g;
// eslint-disable-next-line unicorn/better-regex -- ur wrong
const featureRegex = regexJoin(simpleFeatureRegex, /|/, highlightedFeatureRegex);
const imageRegex = /\.\w{3}$/; // 3 since .png and .gif have 3 letters
// eslint-disable-next-line unicorn/better-regex -- ur dably rong
const rghUploadsRegex = /refined-github[/]refined-github[/]assets[/]/;
// eslint-disable-next-line unicorn/better-regex -- so tripoli wron
const screenshotRegex = regexJoin(imageRegex, /|/, rghUploadsRegex);

function extractDataFromMatch(match: RegExpMatchArray): FeatureMeta {
Expand Down
124 changes: 0 additions & 124 deletions build/verify-features.ts

This file was deleted.

Loading

0 comments on commit d44e947

Please sign in to comment.