ViewersRun takes the rows of Java's viewers from the firmware: a switch's table, a home router's NAT table, each after its reset, and the frames "Pakete verwerfen" drops while it is held. The core writes a frame as Java does and resets the NAT's dynamic entries; the room sends both tables as views, drops while any client holds the button, and sends the dropped frames, begun anew with each start, with the clock their times of day are written from; the editor opens a table with a tap on a switch or home router and holds "Pakete verwerfen" in its toolbar, which now wraps on a phone. This completes F4.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
5.2 KiB
TypeScript
80 lines
5.2 KiB
TypeScript
// The one list of messages: what the room accepts from a client, and what
|
|
// it refuses, each with its reason.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { BROWSER_START, browserTitle, CLIENT_MESSAGE_KINDS, HOST_MESSAGE_KINDS, htmlTitle, isTopic, parseClientMessage, parseHostMessage, type BrowserView } from '../src/index.js';
|
|
|
|
describe("a client's messages", () => {
|
|
it('reads each kind', () => {
|
|
const messages = [
|
|
{ v: 1, kind: 'sub', topics: ['activity', 'trace:port-1', 'app:node-1/terminal'] },
|
|
{ v: 1, kind: 'unsub', topics: ['activity'] },
|
|
{ v: 1, kind: 'start' },
|
|
{ v: 1, kind: 'stop' },
|
|
{ v: 1, kind: 'speed', value: 50 },
|
|
{ v: 1, kind: 'drop', on: true },
|
|
{ v: 1, kind: 'input', node: 'node-1', app: 'terminal', action: 'enter', args: { line: 'ping 1.2.3.4' }, reqId: 'r1' },
|
|
];
|
|
expect(messages.map((each) => each.kind)).toEqual([...CLIENT_MESSAGE_KINDS]);
|
|
for (const each of messages) expect(parseClientMessage(each)).toEqual({ message: each });
|
|
// A start in a language, and the language dropped where it is none.
|
|
for (const language of ['de', 'en']) expect(parseClientMessage({ v: 1, kind: 'start', language })).toEqual({ message: { v: 1, kind: 'start', language } });
|
|
expect(parseClientMessage({ v: 1, kind: 'stop', language: 'en' })).toEqual({ message: { v: 1, kind: 'stop' } });
|
|
});
|
|
|
|
it('names what is wrong', () => {
|
|
const code = (value: unknown) => ('error' in parseClientMessage(value) ? (parseClientMessage(value) as { error: { code: string } }).error.code : 'ok');
|
|
expect(code(null)).toBe('badMessage');
|
|
expect(code([])).toBe('badMessage');
|
|
expect(code({ v: 2, kind: 'start' })).toBe('badVersion');
|
|
expect(code({ v: 1, kind: 'reset' })).toBe('unknownKind');
|
|
expect(code({ v: 1, kind: 'sub', topics: 'activity' })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'sub', topics: ['activity', 'trace:'] })).toBe('badTopic');
|
|
expect(code({ v: 1, kind: 'unsub', topics: ['app:node/paint'] })).toBe('badTopic');
|
|
expect(code({ v: 1, kind: 'start', language: 'fr' })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'drop', on: 'yes' })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'sub', topics: ['dropped', 'app:node/sat', 'app:node/nat'] })).toBe('ok');
|
|
expect(code({ v: 1, kind: 'start', language: null })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'speed', value: '50' })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'speed', value: Number.NaN })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'input', node: 'n', app: 'terminal', action: 'enter', args: {} })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'input', node: 'n', app: 'terminal', action: 'enter', args: [], reqId: 'r' })).toBe('badMessage');
|
|
expect(code({ v: 1, kind: 'input', node: 'n', app: 'paint', action: 'enter', args: {}, reqId: 'r' })).toBe('unknownApp');
|
|
});
|
|
|
|
it('knows its topics', () => {
|
|
expect(['activity', 'trace:x', 'app:n/gnutella', 'app:n/webBrowser', 'node:n'].every(isTopic)).toBe(true);
|
|
expect(['', 'trace:', 'app:/terminal', 'app:n/', 'app:n/terminal/x', 'node:', 'node:n/x', 'nodes:n', 7].some(isTopic)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("a host's messages", () => {
|
|
it('reads each kind, and names an unknown one', () => {
|
|
for (const kind of HOST_MESSAGE_KINDS) expect('message' in parseHostMessage({ v: 1, kind })).toBe(true);
|
|
expect(parseHostMessage({ v: 1, kind: 'nodes' })).toEqual({ error: { code: 'unknownKind', detail: 'kind nodes' } });
|
|
expect(parseHostMessage({ v: 0, kind: 'state' })).toEqual({ error: { code: 'badVersion', detail: 'version 0' } });
|
|
});
|
|
});
|
|
|
|
describe("the browser window's view", () => {
|
|
it("reads an HTML page's first title as it is written, none where it is empty", () => {
|
|
expect(htmlTitle('<html><head><TITLE lang="de">Links & mehr</TITLE><title>Zwei</title></head></html>')).toBe('Links & mehr');
|
|
expect(htmlTitle('<title>\nRouting-Tabelle\n </title >')).toBe('\nRouting-Tabelle\n ');
|
|
expect(htmlTitle('<title><b>Fett</b> und mehr</title>')).toBe('Fett und mehr');
|
|
expect(htmlTitle('<title> </title>')).toBeUndefined();
|
|
expect(htmlTitle('<titles>Nein</titles><p>Kein Titel</p>')).toBeUndefined();
|
|
});
|
|
|
|
it("titles the window with the program's name, then the address being loaded, then the page's title once it came", () => {
|
|
const pageTitle = (page: BrowserView['page']) => (page.kind === 'html' ? htmlTitle(page.html) : page.kind === 'unreachable' ? 'Fehler' : undefined);
|
|
expect(browserTitle(BROWSER_START, 'Webbrowser', pageTitle)).toBe('Webbrowser');
|
|
const shown: BrowserView = { address: 'http://a/', loading: 'http://a/', arrived: true, page: { kind: 'html', html: '<title>A</title>', images: {} } };
|
|
expect(browserTitle(shown, 'Webbrowser', pageTitle)).toBe('A');
|
|
// The next load: its address, while the page before still shows.
|
|
expect(browserTitle({ ...shown, loading: 'http://b/', arrived: false }, 'Webbrowser', pageTitle)).toBe('http://b/');
|
|
// A page without a title keeps the address.
|
|
expect(browserTitle({ ...shown, page: { kind: 'text', text: 'x' } }, 'Webbrowser', pageTitle)).toBe('http://a/');
|
|
expect(browserTitle({ ...shown, page: { kind: 'unreachable' } }, 'Webbrowser', pageTitle)).toBe('Fehler');
|
|
});
|
|
});
|