Skip to content

Commit

Permalink
Improve format switching with default content (#1858)
Browse files Browse the repository at this point in the history
* Improve query param handling

* lints

* hmm

* hmm

* hmm

* hmm

* Lint:fix
  • Loading branch information
NullVoxPopuli authored Nov 8, 2024
1 parent 76065a5 commit 05ce892
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 65 deletions.
4 changes: 4 additions & 0 deletions apps/repl/app/snippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const ALL = [

export const NAMES = ALL.map((demo) => demo.label);

export const LOADED = new Set([DEFAULT_SNIPPET]);

export async function getFromLabel(label: string): Promise<string> {
let entry = ALL.find((entry) => entry.label === label);

Expand All @@ -53,5 +55,7 @@ export async function getFromLabel(label: string): Promise<string> {
let response = await fetch(path);
let text = await response.text();

LOADED.add(text);

return text;
}
101 changes: 43 additions & 58 deletions apps/repl/app/utils/editor-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,8 @@ export class FileURIComponent {
#timeout?: ReturnType<typeof setTimeout>;
#queuedFn?: () => void;

/**
* Debounce so we are kinder on the CPU
*/
queue = (rawText: string, format: Format) => {
if (this.#timeout) clearTimeout(this.#timeout);

this.#queuedFn = () => {
if (isDestroyed(this) || isDestroying(this)) return;

this.set(rawText, format);
this.#queuedFn = undefined;
queueTokens.forEach((token) => queueWaiter.endAsync(token));
};

this.#timeout = setTimeout(this.#queuedFn, DEBOUNCE_MS);
queueTokens.push(queueWaiter.beginAsync());
this.set(rawText, format);
};

#flush = () => {
Expand All @@ -172,64 +158,63 @@ export class FileURIComponent {
return base ?? window.location.toString();
};

#lastQPs: URLSearchParams | undefined;
#updateWaiter: unknown;
#frame?: number;
#qps: URLSearchParams | undefined;
#updateQPs = async (rawText: string, format: Format) => {
let isFast = new Date().getTime() - this.#rapidCallTime < 100;

if (!isFast) {
this.#rapidCallQPs = [];
this.#rapidCallCount = 0;
this.#rapidCallTime = -Infinity;
}
if (this.#frame) cancelAnimationFrame(this.#frame);
if (!this.#updateWaiter) this.#updateWaiter = queueWaiter.beginAsync();

let encoded = compressToEncodedURIComponent(rawText);
let qps = new URLSearchParams(location.search);

if (isFast && this.#rapidCallCount > 1) {
let isIrrelevant =
this.#lastQPs &&
[...qps.entries()].every(([key, value]) => {
return this.#lastQPs?.get(key) === value;
});
qps.set('c', encoded);
qps.delete('t');
qps.set('format', formatFrom(format));

if (isIrrelevant) return;
this.#qps = {
...this.#qps,
...qps,
};

console.debug(this.#rapidCallQPs);
this.#frame = requestAnimationFrame(async () => {
if (isDestroyed(this) || isDestroying(this)) {
queueWaiter.endAsync(this.#updateWaiter);
this.#updateWaiter = null;

let error = new Error('Too many rapid query param changes');
return;
}

console.debug(error.stack);
throw error;
}
/**
* Debounce so we are kinder on the CPU
*/
await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS));

this.#rapidCallTime = new Date().getTime();
this.#rapidCallCount++;
this.#rapidCallQPs.push(qps);
if (isDestroyed(this) || isDestroying(this)) {
queueWaiter.endAsync(this.#updateWaiter);
this.#updateWaiter = null;

qps.set('c', encoded);
qps.delete('t');
qps.set('format', formatFrom(format));
return;
}

this.#lastQPs = qps;
queueWaiter.endAsync(this.#updateWaiter);
this.#updateWaiter = null;

// On initial load, if we call #updateQPs,
// we may not have a currentURL, because the first transition has yet to complete
let base = this.router.currentURL?.split('?')[0];
// On initial load, if we call #updateQPs,
// we may not have a currentURL, because the first transition has yet to complete
let base = this.router.currentURL?.split('?')[0];

if (macroCondition(isTesting())) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
base ??= (this.router as any) /* private API? */?.location?.path;
} else {
base ??= window.location.pathname;
}
if (macroCondition(isTesting())) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
base ??= (this.router as any) /* private API? */?.location?.path;
} else {
base ??= window.location.pathname;
}

let next = `${base}?${qps}`;
let next = `${base}?${qps}`;

this.router.replaceWith(next);
this.#text = rawText;
this.router.replaceWith(next);
this.#text = rawText;
});
};

#rapidCallTime = -Infinity;
#rapidCallCount = 0;
#rapidCallQPs: unknown[] = [];
}
64 changes: 64 additions & 0 deletions apps/repl/tests/application/-page/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { assert } from '@ember/debug';
import { currentURL, settled, visit } from '@ember/test-helpers';

import { PageObject } from 'fractal-page-object';
import { decompressFromEncodedURIComponent } from 'lz-string';

import { s } from './-helpers';
import { DemoSelect } from './demo-select';
Expand All @@ -15,4 +19,64 @@ export class Page extends PageObject {
selectDemo(text: string) {
return this.demo.select(text);
}

async expectRedirectToContent(
to: string,
{ c, t, format }: { t?: string; c?: string; format?: string } = {}
) {
let sawExpectedError = false;

try {
await visit(to);
} catch (e) {
assert('Expected error to be an object', typeof e === 'object' && e !== null);
assert(
'Expected error to have a message property',
'message' in e && typeof e.message === 'string'
);

let lines = e.message.split('\n');
let first = lines[0];

assert(
`The only expected error is a TransitionAborted. Received: ${first}`,
first === 'TransitionAborted'
);
sawExpectedError = true;
}

assert(`Expected to see a TransitionAborted error, but it did not occur.`, sawExpectedError);

// Allow time for transitions to settle
await settled();

let url = currentURL();

assert(`Expected an URL, got ${url}`, url);

let [, search] = url.split('?');
let query = new URLSearchParams(search);

if (format) {
let f = query.get('format');

assert(`Expected format, ${format}, but got ${f}`, f === format);
}

if (c) {
let lzString = query.get('c');

assert(`Missing c query param`, lzString);

let value = decompressFromEncodedURIComponent(lzString);

assert(`QP's c did not match expected text`, c === value);
}

if (t) {
let text = query.get('t');

assert(`QP's t did not match expected text`, text === t);
}
}
}
17 changes: 13 additions & 4 deletions apps/repl/tests/application/editor-format-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@ module('Editor > Format', function (hooks) {
});

test('defaults to glimdown', async function (assert) {
await visit('/edit');
await page.expectRedirectToContent('/edit', {
format: 'glimdown',
});

await page.editor.load();

assert.strictEqual(page.editor.format, 'glimdown');
});

test('when choosing a format, text is required -- otherwise glimdown is chosen', async function (assert) {
await visit('/edit?format=gjs');
await page.expectRedirectToContent('/edit?format=gjs', {
format: 'glimdown',
});

await page.editor.load();
assert.strictEqual(page.editor.format, 'glimdown');
Expand All @@ -50,7 +55,9 @@ module('Editor > Format', function (hooks) {
});

test('can start with glimdown, and change to gjs', async function (assert) {
await visit('/edit');
await page.expectRedirectToContent(`/edit`, {
format: 'glimdown',
});
await page.editor.load();

assert.strictEqual(page.editor.format, 'glimdown');
Expand All @@ -62,7 +69,9 @@ module('Editor > Format', function (hooks) {
});

test('can start with glimdown, and is able to change formats via the URL', async function (assert) {
await visit('/edit');
await page.expectRedirectToContent(`/edit`, {
format: 'glimdown',
});
await page.editor.load();

assert.strictEqual(page.editor.format, 'glimdown');
Expand Down
6 changes: 3 additions & 3 deletions apps/repl/tests/application/output-demos-test.gts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert as debugAssert } from '@ember/debug';
import { settled, visit } from '@ember/test-helpers';
import { settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';

Expand Down Expand Up @@ -41,7 +41,7 @@ module('Output > Demos', function (hooks) {
test(demo.label, async function (assert) {
this.owner.register('template:edit', Route(<template><DemoSelect /></template>));

await visit('/edit');
await page.expectRedirectToContent('/edit');
await page.selectDemo(demo.label);

let { queryParams = {} } = getService('router').currentRoute ?? {};
Expand Down Expand Up @@ -80,7 +80,7 @@ module('Output > Demos', function (hooks) {
)
);

await visit('/edit');
await page.expectRedirectToContent('/edit');

debugAssert(`setParentFrame did not get set`, setParentFrame);
debugAssert(`makeComponent did not get set`, makeComponent);
Expand Down

0 comments on commit 05ce892

Please sign in to comment.