feat(editor): F4.6e the e-mail program and e-mail server on a computer's desktop
The oracle MailWindowRun uses Java's e-mail windows as a person does and answers their dialogs. The core sets and saves the program's account, deletes a mail, checks a form as Java's window does, and gives the server its domain, new and removed accounts and the window's own line; the room sends both programs' views and takes their actions; the editor has both windows. Better than Java: a name field for a new account, a user name the server has is refused, deleting hits the folder shown, a new domain goes into the accounts file at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -196,7 +196,7 @@ describe('a simulation room', () => {
|
||||
expect(reason({ node: 'nobody', app: 'terminal', action: 'enter', args: { line: 'x' } })).toMatchObject({ code: 'unknownNode' });
|
||||
expect(reason({ node: node('Switch 1').id, app: 'terminal', action: 'enter', args: { line: 'x' } })).toMatchObject({ code: 'unknownApp' });
|
||||
expect(reason({ node: notebook, app: 'terminal', action: 'paint' })).toMatchObject({ code: 'unknownAction' });
|
||||
expect(reason({ node: notebook, app: 'emailClient', action: 'send' })).toMatchObject({ code: 'unknownAction' });
|
||||
expect(reason({ node: notebook, app: 'gnutella', action: 'search' })).toMatchObject({ code: 'unknownAction' });
|
||||
expect(reason({ node: notebook, app: 'terminal', action: 'enter', args: { line: 7 } })).toMatchObject({ code: 'badArgs' });
|
||||
expect(reason({ node: notebook, app: 'terminal', action: 'interrupt' })).toEqual({ v: 1, kind: 'ack', reqId: 'r' });
|
||||
send({ kind: 'nonsense', reqId: 'q' });
|
||||
@@ -458,7 +458,7 @@ describe("a simulation room's browser and web server windows", () => {
|
||||
[server, 'webServer', 'setVirtualHost', { row: 0, column: 1, value: 2 }, 'badArgs'],
|
||||
[server, 'webServer', 'restart', {}, 'unknownAction'],
|
||||
[notebook, 'webServer', 'setActive', { active: true }, 'unknownApp'],
|
||||
[server, 'emailServer', 'setActive', { active: true }, 'unknownAction'],
|
||||
[server, 'echoServer', 'setActive', { active: true }, 'unknownAction'],
|
||||
];
|
||||
for (const [target, app, action, args, code] of cases) {
|
||||
send({ kind: 'input', node: target, app, action, args, reqId: `${app}.${action}.${JSON.stringify(args)}` });
|
||||
@@ -467,3 +467,93 @@ describe("a simulation room's browser and web server windows", () => {
|
||||
expect(ofKind(got, 'ack')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a simulation room's e-mail windows", () => {
|
||||
const views = (got: readonly HostMessage[], node: Id, app: string) => ofKind(got, 'view').filter((each) => each.node === node && each.app === app).map((each) => each.view);
|
||||
|
||||
it('sends the e-mail program and server as views, and takes their actions', () => {
|
||||
const { node, send, run, got } = setUp(exampleDoc('java-examples/email_komplett.fls'));
|
||||
const one = node('Rechner 1').id;
|
||||
const two = node('Rechner 2').id;
|
||||
const server = node('Mail-Server (filius.de)').id;
|
||||
send({ kind: 'sub', topics: [`app:${one}/emailClient`, `app:${two}/emailClient`, `app:${server}/emailServer`] });
|
||||
expect(views(got, server, 'emailServer')).toEqual([null]);
|
||||
send({ kind: 'start' });
|
||||
run(TICK_MILLISECONDS);
|
||||
expect(views(got, server, 'emailServer').at(-1)).toEqual({ active: true, domain: 'filius.de', accounts: [{ username: 'rechner1', mails: expect.any(Number) }, { username: 'rechner2', mails: expect.any(Number) }] });
|
||||
const mails = (views(got, server, 'emailServer').at(-1) as { accounts: { mails: number }[] }).accounts[1]!.mails;
|
||||
expect(views(got, one, 'emailClient').at(-1)).toMatchObject({ account: { username: 'rechner1', email: 'rechner1@filius.de' } });
|
||||
const act = (target: Id, app: string, action: string, args: object, reqId: string) => send({ kind: 'input', node: target, app, action, args, reqId });
|
||||
act(one, 'emailClient', 'send', { to: 'rechner2@filius.de', cc: '', bcc: '', subject: 'Hallo', text: 'Zeile' }, 'send');
|
||||
run(20_000);
|
||||
expect(ofKind(got, 'ack').map((each) => each.reqId)).toEqual(['send']);
|
||||
expect((views(got, one, 'emailClient').at(-1) as { sent: { subject: string }[] }).sent.map((mail) => mail.subject)).toContain('Hallo');
|
||||
expect((views(got, server, 'emailServer').at(-1) as { accounts: { mails: number }[] }).accounts[1]!.mails).toBe(mails + 1);
|
||||
act(two, 'emailClient', 'fetch', {}, 'fetch');
|
||||
run(20_000);
|
||||
const inbox = () => (views(got, two, 'emailClient').at(-1) as { inbox: { subject: string }[] }).inbox;
|
||||
expect(inbox().map((mail) => mail.subject)).toContain('Hallo');
|
||||
const before = inbox().length;
|
||||
act(two, 'emailClient', 'removeMail', { folder: 'inbox', index: 0 }, 'remove');
|
||||
expect(inbox()).toHaveLength(before - 1);
|
||||
// The program's lines go on as lines.
|
||||
expect(ofKind(got, 'app').some((each) => each.app === 'emailClient' && each.node === one && each.lines.length > 0)).toBe(true);
|
||||
act(two, 'emailClient', 'setAccount', { name: 'Zwei Zwo', email: 'zwei@filius.de', pop3Host: '141.99.50.232', pop3Port: '110', smtpHost: '141.99.50.232', smtpPort: '25', username: 'zwei', password: 'x' }, 'account');
|
||||
expect(views(got, two, 'emailClient').at(-1)).toMatchObject({ account: { firstName: 'Zwei', lastName: 'Zwo', username: 'zwei', pop3Port: 110 } });
|
||||
|
||||
// The server: switched off with the window's line, its domain set, an account made and removed.
|
||||
act(server, 'emailServer', 'setActive', { active: false }, 'off');
|
||||
act(server, 'emailServer', 'setDomain', { domain: 'schule.de' }, 'domain');
|
||||
act(server, 'emailServer', 'addAccount', { username: 'neu', password: 'pw', firstName: 'Vorname', lastName: 'Nachname' }, 'add');
|
||||
act(server, 'emailServer', 'removeAccount', { username: 'rechner1' }, 'removeAccount');
|
||||
expect(views(got, server, 'emailServer').at(-1)).toEqual({ active: false, domain: 'schule.de', accounts: [{ username: 'rechner2', mails: expect.any(Number) }, { username: 'neu', mails: 0 }] });
|
||||
const lines = ofKind(got, 'app').filter((each) => each.app === 'emailServer').flatMap((each) => each.lines as { kind: string }[]);
|
||||
expect(lines.at(-1)).toMatchObject({ kind: 'switched', active: false });
|
||||
});
|
||||
|
||||
it('names a wrong action in the e-mail windows', () => {
|
||||
const { node, send, run, got } = setUp(exampleDoc('java-examples/email_komplett.fls'));
|
||||
const one = node('Rechner 1').id;
|
||||
const server = node('Mail-Server (filius.de)').id;
|
||||
send({ kind: 'start' });
|
||||
run(TICK_MILLISECONDS);
|
||||
const account = { name: 'A', email: 'a@filius.de', pop3Host: 'h', pop3Port: '110', smtpHost: 'h', smtpPort: '25', username: 'a', password: 'p' };
|
||||
const cases: [string, string, string, Record<string, unknown>, string][] = [
|
||||
[one, 'emailClient', 'send', { to: 'kein', cc: '', bcc: '', subject: '', text: '' }, 'badArgs'],
|
||||
[one, 'emailClient', 'send', { to: '', cc: '', bcc: '', subject: '', text: '' }, 'badArgs'],
|
||||
[one, 'emailClient', 'send', { to: 'a@b.de', cc: '', bcc: '', subject: '' }, 'badArgs'],
|
||||
[one, 'emailClient', 'setAccount', { ...account, pop3Port: '0' }, 'badArgs'],
|
||||
[one, 'emailClient', 'setAccount', { ...account, password: 5 }, 'badArgs'],
|
||||
[one, 'emailClient', 'removeMail', { folder: 'drafts', index: 0 }, 'badArgs'],
|
||||
[one, 'emailClient', 'removeMail', { folder: 'inbox', index: 0.5 }, 'badArgs'],
|
||||
[one, 'emailClient', 'reply', {}, 'unknownAction'],
|
||||
[server, 'emailClient', 'fetch', {}, 'unknownApp'],
|
||||
[server, 'emailServer', 'setActive', { active: 1 }, 'badArgs'],
|
||||
[server, 'emailServer', 'setDomain', { domain: 'neu.de' }, 'badArgs'],
|
||||
[server, 'emailServer', 'setDomain', { domain: 3 }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: 'rechner1', password: 'p', firstName: '', lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: '', password: 'p', firstName: '', lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: 'a', password: '', firstName: '', lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: 'a b', password: 'p', firstName: '', lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: 'a\tb', password: 'p', firstName: '', lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'addAccount', { username: 'a', password: 'p', firstName: 1, lastName: '' }, 'badArgs'],
|
||||
[server, 'emailServer', 'removeAccount', { username: 'niemand' }, 'badArgs'],
|
||||
[server, 'emailServer', 'removeAccount', {}, 'badArgs'],
|
||||
[server, 'emailServer', 'restart', {}, 'unknownAction'],
|
||||
[one, 'emailServer', 'setActive', { active: true }, 'unknownApp'],
|
||||
];
|
||||
for (const [target, app, action, args, code] of cases) {
|
||||
const reqId = `${app}.${action}.${JSON.stringify(args)}`;
|
||||
send({ kind: 'input', node: target, app, action, args, reqId });
|
||||
expect(ofKind(got, 'error').at(-1), reqId).toMatchObject({ code, reqId });
|
||||
}
|
||||
expect(ofKind(got, 'ack')).toEqual([]);
|
||||
// Off, the server takes a domain, and only one Java's pattern takes.
|
||||
send({ kind: 'input', node: server, app: 'emailServer', action: 'setActive', args: { active: false }, reqId: 'off' });
|
||||
send({ kind: 'input', node: server, app: 'emailServer', action: 'setDomain', args: { domain: 'kein domain!' }, reqId: 'bad' });
|
||||
expect(ofKind(got, 'error').at(-1)).toMatchObject({ code: 'badArgs', reqId: 'bad' });
|
||||
send({ kind: 'input', node: server, app: 'emailServer', action: 'setDomain', args: { domain: 'neu.de' }, reqId: 'good' });
|
||||
expect(ofKind(got, 'ack').map((each) => each.reqId)).toEqual(['off', 'good']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user