Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ From a clean checkout of the intended production commit:

```bash
python3 -m unittest discover -s scripts -p 'test_*.py' -v
node --test scripts/test_catalog_search_events.cjs
python3 scripts/diagnose_growth_deploy.py --root . --output growth-deploy-diagnosis.json
python3 scripts/diagnose_growth_routes.py --output growth-route-diagnosis.json
python3 scripts/check_growth_live.py > growth-live-smoke.json
Expand Down
6 changes: 5 additions & 1 deletion apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -1473,7 +1473,11 @@ <h3>Chaos - Stranger Missions</h3>
});
lastReportedSearch = query;
};
input.addEventListener('input', render);
input.addEventListener('input', () => {
// The browser search clear (×) must allow an identical query next time.
if (!clean(input.value)) lastReportedSearch = '';
render();
});
input.addEventListener('blur', reportSearch);
input.addEventListener('keydown', event => {
if (event.key === 'Enter') reportSearch();
Expand Down
93 changes: 93 additions & 0 deletions scripts/test_catalog_search_events.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';
// Run from repository root: node --test scripts/test_catalog_search_events.cjs
// Execute the actual catalog's input/reporting handlers in an isolated VM.
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');

const html = fs.readFileSync(path.join(__dirname, '..', 'apps.html'), 'utf8');
const start = html.indexOf(' const reportSearch = () => {');
const after = html.indexOf(" input.addEventListener('keydown', event => {", start);
const end = html.indexOf(' });', after) + ' });'.length;
assert.ok(start >= 0 && after > start && end > after, 'search handlers are present');
const handlers = html.slice(start, end);

function setup() {
const events = [];
const listeners = {};
const input = {
value: '',
addEventListener(type, callback) { listeners[type] = callback; },
};
const context = {
input,
clean: v => v.normalize('NFKC').toLocaleLowerCase().trim(),
render: () => 4,
fire: (name, payload) => events.push({ name, ...payload }),
};
vm.runInNewContext("let lastReportedSearch = '';\n" + handlers + "\nglobalThis.commitSearch = reportSearch;", context);
assert.equal(typeof listeners.input, 'function');
assert.equal(typeof listeners.blur, 'function');
assert.equal(typeof listeners.keydown, 'function');
return { input, events, listeners, commit: context.commitSearch };
}

test('typing does not send an unfinished search', () => {
const h = setup();
h.input.value = 'do';
h.listeners.input();
assert.equal(h.events.length, 0);
});

test('Enter emits exactly one committed search', () => {
const h = setup();
h.input.value = 'dotchi';
h.listeners.input();
h.listeners.keydown({ key: 'Enter' });
h.listeners.blur();
assert.equal(h.events.length, 1);
assert.equal(h.events[0].name, 'catalog_search_used');
assert.equal(h.events[0].results, 4);
assert.equal(h.events[0].search_length_band, 'long');
});

test('browser search clear allows the same query to count again', () => {
const h = setup();
h.input.value = 'dotchi';
h.listeners.input();
h.listeners.blur();
h.input.value = '';
h.listeners.input();
h.input.value = 'dotchi';
h.listeners.input();
h.listeners.blur();
assert.equal(h.events.length, 2);
});

test('clearing a search does not emit a blank query', () => {
const h = setup();
h.input.value = ' ';
h.listeners.input();
h.listeners.blur();
assert.equal(h.events.length, 0);
});

test('repeated blur without a cleared search is deduplicated', () => {
const h = setup();
h.input.value = 'sleep';
h.listeners.input();
h.listeners.blur();
h.listeners.blur();
assert.equal(h.events.length, 1);
});

test('committed searches never send raw query text', () => {
const h = setup();
h.input.value = 'sensitive private query';
h.listeners.input();
h.listeners.blur();
assert.equal(h.events.length, 1);
assert.equal(JSON.stringify(h.events).includes('sensitive'), false);
});
Loading