toiljs 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ import path from 'node:path';
2
+
3
+ import { createServer } from 'vite';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { loadConfig } from '../src/compiler/config';
7
+ import {
8
+ emailsVersion,
9
+ listEmails,
10
+ previewShellHtml,
11
+ renderEmailByName,
12
+ } from '../src/compiler/email-preview';
13
+ import { createViteConfig } from '../src/compiler/vite';
14
+
15
+ const EXAMPLE = path.resolve(__dirname, '../examples/basic');
16
+
17
+ describe('email preview end-to-end (examples/basic)', () => {
18
+ it('lists Welcome and inlines its emails/styles/email.css; client/* alias resolves', async () => {
19
+ const cfg = await loadConfig({ root: EXAMPLE });
20
+ const items = listEmails(cfg);
21
+ expect(items.map((i) => i.name)).toContain('Welcome');
22
+
23
+ const server = await createServer({
24
+ ...(await createViteConfig(cfg)),
25
+ server: { middlewareMode: true, hmr: false },
26
+ appType: 'custom',
27
+ logLevel: 'silent',
28
+ });
29
+ try {
30
+ const r = await renderEmailByName(server, cfg, 'Welcome');
31
+ if (!r) throw new Error('Welcome did not render');
32
+ // tokens discovered from props
33
+ expect(r.tokens).toEqual(['code', 'name']);
34
+ // subject token template
35
+ expect(r.subject).toBe('Welcome, {{name}}!');
36
+ // .email-title { color: #111827 } from emails/styles/email.css inlined onto the <h1>
37
+ expect(r.html).toMatch(/<h1[^>]*style="[^"]*color:\s*#111827/i);
38
+ // .email-card backgroundColor inlined onto the <table>
39
+ expect(r.html).toMatch(/<table[^>]*style="[^"]*background-color:\s*#f6f7f9/i);
40
+
41
+ // The `client/*` reuse alias still resolves project CSS (the documented
42
+ // `import 'client/styles/…'` path), independent of where the demo keeps its styles.
43
+ const aliased = (await server.ssrLoadModule('client/styles/main.css?inline')) as {
44
+ default?: unknown;
45
+ };
46
+ expect(typeof aliased.default).toBe('string');
47
+ } finally {
48
+ await server.close();
49
+ }
50
+ }, 30000);
51
+
52
+ it('emailsVersion is a non-empty mtime:count fingerprint', async () => {
53
+ const cfg = await loadConfig({ root: EXAMPLE });
54
+ const v = emailsVersion(cfg);
55
+ expect(v).toMatch(/^\d+(\.\d+)?:\d+$/);
56
+ // at least Welcome.tsx + the client CSS files were counted
57
+ expect(Number(v.split(':')[1])).toBeGreaterThan(1);
58
+ });
59
+
60
+ it('the preview shell wires the dev endpoints', () => {
61
+ const html = previewShellHtml();
62
+ expect(html).toContain("var BASE = '/__toil/emails'");
63
+ for (const frag of ["BASE + '/list'", "BASE + '/render?name='", "BASE + '/version'"]) {
64
+ expect(html).toContain(frag);
65
+ }
66
+ expect(html).toContain('/__toil/open?file='); // open-in-editor
67
+ });
68
+ });
@@ -0,0 +1,58 @@
1
+ import { createElement, type ReactElement } from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { __test } from '../src/compiler/emails';
6
+
7
+ const render = (el: unknown): string => renderToStaticMarkup(el as ReactElement);
8
+
9
+ describe('renderModule', () => {
10
+ it('discovers props as {{tokens}} and renders placeholders (alpha-sorted, deduped)', async () => {
11
+ const mod = {
12
+ default: (p: { name: string; code: string }) =>
13
+ createElement('p', null, `Hi ${p.name}, code ${p.code}`),
14
+ };
15
+ const r = await __test.renderModule('Welcome', mod, render);
16
+ if (!r) throw new Error('expected a rendered email');
17
+ expect(r.tokens).toEqual(['code', 'name']);
18
+ expect(r.html).toContain('{{name}}');
19
+ expect(r.html).toContain('{{code}}');
20
+ });
21
+
22
+ it('returns null when there is no default-exported component', async () => {
23
+ expect(await __test.renderModule('X', { default: 'nope' }, render)).toBeNull();
24
+ });
25
+
26
+ it('inlines imported CSS into element style="" (the reuse path)', async () => {
27
+ const mod = { default: () => createElement('h1', { className: 'email-title' }, 'Hello') };
28
+ const css = '.email-title { color: #111827; font-size: 22px; }';
29
+ const r = await __test.renderModule('Styled', mod, render, css);
30
+ if (!r) throw new Error('expected a rendered email');
31
+ // The class rule is moved onto the element as an inline style by the inliner.
32
+ expect(r.html).toMatch(/<h1[^>]*style="[^"]*color:\s*#111827/i);
33
+ expect(r.html).toMatch(/font-size:\s*22px/i);
34
+ });
35
+ });
36
+
37
+ describe('renderModuleSource', () => {
38
+ it('generates a typed Emails.<Name>.send with alpha-sorted token params', () => {
39
+ const src = __test.renderModuleSource([
40
+ {
41
+ name: 'Welcome',
42
+ subject: 'Welcome, {{name}}!',
43
+ html: '<p>{{code}}</p>',
44
+ text: 'code {{code}}',
45
+ tokens: ['code', 'name'],
46
+ purpose: 'welcome',
47
+ },
48
+ ]);
49
+ expect(src).toContain('export namespace Emails {');
50
+ expect(src).toContain('export namespace Welcome {');
51
+ expect(src).toContain(
52
+ 'export function send(to: string, code: string, name: string, purpose: string = "welcome")',
53
+ );
54
+ expect(src).toContain(
55
+ 'return new EmailTemplate(SUBJECT, TEXT, HTML).send(to, __v, purpose);',
56
+ );
57
+ });
58
+ });