mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-22 12:11:27 -07:00
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { createControllerConfigForm } from './controller-config-form.js';
|
|
|
|
function createClassList(initialTokens: string[] = []) {
|
|
const tokens = new Set(initialTokens);
|
|
return {
|
|
add: (...entries: string[]) => {
|
|
for (const entry of entries) tokens.add(entry);
|
|
},
|
|
remove: (...entries: string[]) => {
|
|
for (const entry of entries) tokens.delete(entry);
|
|
},
|
|
toggle: (entry: string, force?: boolean) => {
|
|
if (force === undefined) {
|
|
if (tokens.has(entry)) tokens.delete(entry);
|
|
else tokens.add(entry);
|
|
return tokens.has(entry);
|
|
}
|
|
if (force) tokens.add(entry);
|
|
else tokens.delete(entry);
|
|
return force;
|
|
},
|
|
contains: (entry: string) => tokens.has(entry),
|
|
};
|
|
}
|
|
|
|
function createFakeElement() {
|
|
const attributes = new Map<string, string>();
|
|
const el = {
|
|
className: '',
|
|
textContent: '',
|
|
_innerHTML: '',
|
|
value: '',
|
|
disabled: false,
|
|
selected: false,
|
|
type: '',
|
|
children: [] as any[],
|
|
listeners: new Map<string, Array<(e?: any) => void>>(),
|
|
classList: createClassList(),
|
|
appendChild(child: any) {
|
|
this.children.push(child);
|
|
return child;
|
|
},
|
|
addEventListener(type: string, listener: (e?: any) => void) {
|
|
const existing = this.listeners.get(type) ?? [];
|
|
existing.push(listener);
|
|
this.listeners.set(type, existing);
|
|
},
|
|
dispatch(type: string) {
|
|
const fakeEvent = { stopPropagation: () => {}, preventDefault: () => {} };
|
|
for (const listener of this.listeners.get(type) ?? []) {
|
|
listener(fakeEvent);
|
|
}
|
|
},
|
|
setAttribute(name: string, value: string) {
|
|
attributes.set(name, value);
|
|
},
|
|
getAttribute(name: string) {
|
|
return attributes.get(name) ?? null;
|
|
},
|
|
};
|
|
Object.defineProperty(el, 'innerHTML', {
|
|
get() {
|
|
return el._innerHTML;
|
|
},
|
|
set(v: string) {
|
|
el._innerHTML = v;
|
|
if (v === '') el.children.length = 0;
|
|
},
|
|
});
|
|
return el;
|
|
}
|
|
|
|
test('controller config form renders rows and dispatches learn clear reset callbacks', () => {
|
|
const previousDocumentDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
|
Object.defineProperty(globalThis, 'document', {
|
|
configurable: true,
|
|
value: {
|
|
createElement: () => createFakeElement(),
|
|
},
|
|
});
|
|
|
|
try {
|
|
const calls: string[] = [];
|
|
const container = createFakeElement();
|
|
const form = createControllerConfigForm({
|
|
container: container as never,
|
|
getBindings: () =>
|
|
({
|
|
toggleLookup: { kind: 'button', buttonIndex: 0 },
|
|
closeLookup: { kind: 'button', buttonIndex: 1 },
|
|
toggleKeyboardOnlyMode: { kind: 'button', buttonIndex: 3 },
|
|
mineCard: { kind: 'button', buttonIndex: 2 },
|
|
quitMpv: { kind: 'button', buttonIndex: 6 },
|
|
previousAudio: { kind: 'none' },
|
|
nextAudio: { kind: 'button', buttonIndex: 5 },
|
|
playCurrentAudio: { kind: 'button', buttonIndex: 4 },
|
|
toggleMpvPause: { kind: 'button', buttonIndex: 9 },
|
|
leftStickHorizontal: { kind: 'axis', axisIndex: 0, dpadFallback: 'horizontal' },
|
|
leftStickVertical: { kind: 'axis', axisIndex: 1, dpadFallback: 'vertical' },
|
|
rightStickHorizontal: { kind: 'axis', axisIndex: 3, dpadFallback: 'none' },
|
|
rightStickVertical: { kind: 'axis', axisIndex: 4, dpadFallback: 'none' },
|
|
}) as never,
|
|
getLearningActionId: () => 'toggleLookup',
|
|
getDpadLearningActionId: () => null,
|
|
onLearn: (actionId, bindingType) => calls.push(`learn:${actionId}:${bindingType}`),
|
|
onClear: (actionId) => calls.push(`clear:${actionId}`),
|
|
onReset: (actionId) => calls.push(`reset:${actionId}`),
|
|
onDpadLearn: (actionId) => calls.push(`dpadLearn:${actionId}`),
|
|
onDpadClear: (actionId) => calls.push(`dpadClear:${actionId}`),
|
|
onDpadReset: (actionId) => calls.push(`dpadReset:${actionId}`),
|
|
});
|
|
|
|
form.render();
|
|
|
|
// In the new compact list layout, children are:
|
|
// [0] group header, [1] first binding row (auto-expanded because learning), [2] edit panel, [3] next row, ...
|
|
const firstRow = container.children[1];
|
|
assert.equal(firstRow.classList.contains('expanded'), true);
|
|
|
|
// After expanding, the edit panel is inserted after the row:
|
|
// [0] group header, [1] row, [2] edit panel, [3] next row, ...
|
|
const editPanel = container.children[2];
|
|
// editPanel > inner > actions > learnButton
|
|
const inner = editPanel.children[0];
|
|
const actions = inner.children[1];
|
|
const learnButton = actions.children[0];
|
|
learnButton.dispatch('click');
|
|
actions.children[1].dispatch('click');
|
|
actions.children[2].dispatch('click');
|
|
|
|
assert.deepEqual(calls, [
|
|
'learn:toggleLookup:discrete',
|
|
'clear:toggleLookup',
|
|
'reset:toggleLookup',
|
|
]);
|
|
} finally {
|
|
if (previousDocumentDescriptor) {
|
|
Object.defineProperty(globalThis, 'document', previousDocumentDescriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, 'document');
|
|
}
|
|
}
|
|
});
|