Skip to content

Commit

Permalink
Huge JSDoc rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
EvidentlyCube committed Oct 17, 2023
1 parent f469e6a commit 83512ca
Show file tree
Hide file tree
Showing 28 changed files with 422 additions and 178 deletions.
8 changes: 8 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"module": "ES2022",
"moduleResolution": "node",
"target": "ES2022"
},
"exclude": ["node_modules", "**/node_modules/*"],
}
47 changes: 44 additions & 3 deletions scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,51 @@ async function getTestsList() {
}

async function selectTest(tests) {
const inputToMatches = input => {
return input.split(' ').filter(empty).map(word => new RegExp(escapeRegexp(word), 'gi'));
};

const prompt = new enquirer.AutoComplete({
message: 'Pick test to run',
limit: 10,
choices: tests.map(test => ({ name: test.fullName, value: test.id }))
choices: tests.map(test => ({ name: test.fullName, value: test.id })),
suggest(typed, choices) {
if (!typed) {
return choices;
}

const matches = inputToMatches(typed);

return choices.filter(choice => {
const missingMatch = matches.findIndex(match => !match.test(choice.message));

return missingMatch === -1;
});
},
async render() {
if (this.state.status !== 'pending') {
return await enquirer.Select.prototype.render.call(this);
}
const hl = this.options.highlight || this.styles.complement;

if (!this.input) {
return await enquirer.Select.prototype.render.call(this);
} else {
const matches = inputToMatches(this.input);
const style = message => {
for (const match of matches) {
message = message.replace(match, str => chalk.underline.red(str));
}

return message;
}

const choices = this.choices;
this.choices = choices.map(ch => ({ ...ch, message: style(ch.message) }));
await enquirer.Select.prototype.render.call(this);
this.choices = choices;
}
}
});

const result = await prompt.run();
Expand All @@ -111,7 +152,7 @@ async function selectTest(tests) {

async function runTest(test, debugEnabled) {
return new Promise((resolve, reject) => {
const safeTitle = test.titlePath.map(title => escapeRegex(title)).join(".+");
const safeTitle = test.titlePath.map(title => escapeRegexp(title)).join(".+");
const pwArgs = [
'playwright',
'test',
Expand Down Expand Up @@ -147,6 +188,6 @@ async function runTest(test, debugEnabled) {
});
}

function escapeRegex(string) {
function escapeRegexp(string) {
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
}
11 changes: 9 additions & 2 deletions tests-pw/NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Note 001:
# Note#001:

TiddlyWiki will not update $edit widget if it has focus even when the edited field changed.
That's expected behavior, to avoid the widget from updating while typing in it.
This is a bit of a bummer for our tests which involve things being open in another window,
so as a solution we force blur on the inputs.
so as a solution we force blur on the inputs.

# Note#002:

Playwright fixture support works great when used with TypeScript but is a bit of a hell if
you try to use pure JS. The solution used to have proper code completion in VSCode
was taken from [this comment](https://github.com/microsoft/playwright/issues/7890#issuecomment-1369828521)
by **Andrew Hobson** on Playwright's GitHub issues.
22 changes: 19 additions & 3 deletions tests-pw/common/core/Test.js → tests-pw/common/core/BaseTest.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { test as baseTest } from '@playwright/test';
// @ts-check
import * as base from '@playwright/test';
import { EditionSelector } from './EditionSelector';
import { TiddlyWikiUi } from '../ui/TiddlyWikiUi';
import { TiddlerStore } from './TiddlerStore';
import { TiddlyWikiConfig } from './TiddlyWikiConfig';

export const test = baseTest.extend({
// --------------------
// NOTES.md#002
// --------------------

/**
* @typedef {object} TiddlyWikiTestFixtures
* @property {TiddlerStore} store
* @property {TiddlyWikiConfig} twConfig
* @property {EditionSelector} selectEdition
* @property {TiddlyWikiUi} ui
*/

/** @type {base.Fixtures<TiddlyWikiTestFixtures, {}, base.PlaywrightTestArgs, base.PlaywrightWorkerArgs>} */
export const baseTestFixtures = {
store: async({page}, use) => {
await use(new TiddlerStore(page));
},
Expand All @@ -17,4 +31,6 @@ export const test = baseTest.extend({
ui: async ({page}, use) => {
await use(new TiddlyWikiUi(page));
}
});
};

export const baseTest = base.test.extend(baseTestFixtures);
92 changes: 72 additions & 20 deletions tests-pw/common/core/EditionSelector.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,98 @@
// @ts-check
import { sep, resolve } from 'path';
import { expect } from 'playwright/test';

const docsPath = resolve(`${process.cwd()}${sep}docs${sep}`);
// Windows requires adding a slash at the start, while Linux already has it baked in
const crossPlatformDocsPath = docsPath.replace(/^\/+/, '');

/**
* @typedef {object} EditionEntry
* @property {string} suffix Suffix to add to the `index.html` file
* @property {string} version Version used for asserting the correct file was loaded
* @property {boolean} hasCodeMirror Whether this version includes code mirror
*/

/**
* @type {Object.<string, EditionEntry>}
*/
const SUPPORTED_EDITIONS = {
tw522: getEdition('', '5.2.2', false),
tw522CodeMirror: getEdition('-cm', '5.2.2-CodeMirror', true),
tw530: getEdition('-530', '5.3.0', false),
tw530CodeMirror: getEdition('-530-cm', '5.3.0-CodeMirror', true),
tw531: getEdition('-531', '5.3.1', false),
tw531CodeMirror: getEdition('-531-cm', '5.3.1-CodeMirror', true),
};

/**
* Selects and initializes a specific version of Tiddly Wiki for testing
*/
export class EditionSelector {
/**
* @param {import('playwright-core').Page} page
*/
constructor(page) {
this.page = page;
}

tw522 = async page => this.#goto(page, '', '5.2.2');
tw522CodeMirror = async page => this.#goto(page, '-cm', '5.2.2-CodeMirror');
tw530 = async page => this.#goto(page, '-530', '5.3.0');
tw530CodeMirror = async page => this.#goto(page, '-530-cm', '5.3.0-CodeMirror');
tw531 = async page => this.#goto(page, '-531', '5.3.1');
tw531CodeMirror = async page => this.#goto(page, '-531-cm', '5.3.1-CodeMirror');
/**
* Initializes the given TiddlyWiki edition
*
* @param {string} editionName
* @param {import('playwright-core').Page} [page]
* @return {Promise<void>}
*/
initByName = async (editionName, page = undefined) => {
const edition = SUPPORTED_EDITIONS[editionName];

initByName = async (name, page) => {
if (typeof this[name] !== 'function') {
throw new Error(`Unsupported edition '${name}'`);
if (!edition) {
throw new Error(`Unsupported edition '${editionName}'`);
}

await this[name](page);
await this.#init(page, edition.suffix, edition.version);
}

#goto = async (page, suffix, expectedVersion) => {
/**
* @param {import('playwright-core').Page|undefined} page
* @param {string} suffix
* @param {string} expectedVersion
*/
#init = async (page, suffix, expectedVersion) => {
page = page ?? this.page;

await page.goto(`file:///${crossPlatformDocsPath}/index${suffix}.html`);
await expect(page).toHaveTitle(/Evidently Cube TiddlyWiki5 Plugin Showcase/);
await expect(page.locator('[data-test-id="tw-edition"]')).toHaveText(expectedVersion, {timeout: 300});
};

static getEditions(codeMirrorFilter) {
return [
codeMirrorFilter !== true ? 'tw522' : null,
codeMirrorFilter !== false ? 'tw522CodeMirror' : null,
codeMirrorFilter !== true ? 'tw530' : null,
codeMirrorFilter !== false ? 'tw530CodeMirror' : null,
codeMirrorFilter !== true ? 'tw531' : null,
codeMirrorFilter !== false ? 'tw531CodeMirror' : null,
].filter(x => x);
/**
* Returns a list of TW editions that can be passed to `initByName`.
*
* @param {boolean|undefined} codeMirrorFilter If set to true or false will respectively return editions that
* include Code Mirror or ones that don't. When undefined/left empty it returns all editions.
*
* @returns {string[]}
*/
static getEditions(codeMirrorFilter = undefined) {
return Object.keys(SUPPORTED_EDITIONS)
.filter(id => {
const edition = SUPPORTED_EDITIONS[id];

return codeMirrorFilter === undefined || edition.hasCodeMirror === codeMirrorFilter
});
}
}

/**
* Utility to build a list of supported editions.
*
* @param {string} suffix
* @param {string} version
* @param {boolean} hasCodeMirror
*
* @returns {EditionEntry}
*/
function getEdition(suffix, version, hasCodeMirror) {
return {suffix, version, hasCodeMirror};
}
51 changes: 47 additions & 4 deletions tests-pw/common/core/TiddlerStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import fs from 'fs';
import {sep, resolve} from 'path';
// @ts-check

/**
* @typedef {object} TiddlerStoreFixture
* @property {string} title
*/

/**
* Class for interacting with browser's Tiddler storage, much faster than the UI.
*/
export class TiddlerStore {
/**
* @param {import("@playwright/test").Page} page
Expand All @@ -9,6 +16,11 @@ export class TiddlerStore {
this.page = page;
}

/**
* Create a store for a different page object
* @param {import("playwright-core").Page} page
* @returns {TiddlerStore}
*/
forPage(page) {
return new TiddlerStore(page);
}
Expand All @@ -23,18 +35,41 @@ export class TiddlerStore {
}, tag);
}

/**
* Adds a given fixture as a tiddler. It must be a JS object with at least one field called `title`.
*n
* @param {TiddlerStoreFixture} fixture
* @returns {Promise<string>} Title of the loaded fixture
*/
async loadFixture(fixture) {
if (!fixture) {
throw new Error("Attempted to load fixture but none was given");

} else if (typeof fixture !== 'object') {
throw new Error(`Attempted to load fixture but it was of type '${typeof fixture}' rather than 'object'`);

} else if (!fixture.title) {
throw new Error("Attempted to load fixture but it did not have a `title` field");
}

await this.loadFixtures([fixture]);

return fixture.title;
}

/**
* Adds multiple fixtures as a tiddler. It will graciously handle all errors.
*
* @see loadFixture
* @param {TiddlerStoreFixture[]} fixtures
* @returns {Promise<string[]>} Titles of the loaded fixtures.
*/
async loadFixtures(fixtures) {
fixtures = Array.isArray(fixtures) ? fixtures : [fixtures];

this.page.evaluate(fixtures => {
for (const fixture of fixtures) {
if (!fixture) {
if (!fixture || typeof fixture !== 'object' || !fixture.title) {
continue;
}

Expand All @@ -45,8 +80,16 @@ export class TiddlerStore {
return fixtures.map(fixture => fixture.title);
}

/**
* Updates fields of an existing tiddler, optionally creating it if it doesn't exist.
*
* @param {string} title
* @param {Object.<string, any>} fields
* @param {boolean} allowCreate
* @returns {Promise<void>}
*/
async updateTiddler(title, fields, allowCreate = false) {
return this.page.evaluate(({title, fields, allowCreate}) => {
await this.page.evaluate(({title, fields, allowCreate}) => {
const tiddler = $tw.wiki.getTiddler(title)
?? (allowCreate ? new $tw.Tiddler({title}, fields) : false);

Expand Down
26 changes: 21 additions & 5 deletions tests-pw/common/core/TiddlyWikiConfig.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
import fs from 'fs';
import {sep, resolve} from 'path';
// @ts-check
import { TiddlerStore } from './TiddlerStore';

/**
* Handy function to change certain configuration options in TiddlyWiki without having to pass
* tiddler names.
*/
export class TiddlyWikiConfig {
/**
* @param {import("@playwright/test").Page} page
* @param {import('playwright/test').Page} page
* @param {TiddlerStore} store
*/
constructor(page, store) {
this.page = page;
this.store = store;
}

/**
* Created a configurator for another page object.
* @param {import('playwright/test').Page} page
* @returns TiddlyWikiConfig
*/
forPage(page) {
return new TiddlyWikiConfig(page, this.store.forPage(page));
}

/**
* Controls whether framed editor is used or not.
* @param {boolean} bool
*/
async useFramedEditor(bool) {
this.store.updateTiddler('$:/config/TextEditor/EnableToolbar', {text: bool ? 'yes' : 'no'}, true);
return this.store.updateTiddler('$:/config/TextEditor/EnableToolbar', {text: bool ? 'yes' : 'no'}, true);
}

/**
* Controls whether Code Mirror's Auto Close Tags plugin is active
* @param {boolean} bool
*/
async codeMirrorAutoCloseTags(bool) {
this.store.updateTiddler('$:/config/codemirror/autoCloseTags', {text: bool ? 'true' : 'false'}, true);
return this.store.updateTiddler('$:/config/codemirror/autoCloseTags', {text: bool ? 'true' : 'false'}, true);
}
}
Loading

0 comments on commit 83512ca

Please sign in to comment.