Skip to content

Commit 1ecf4d4

Browse files
authored
Merge pull request #555 from nyaruka/copilot/fix-554
Add markdown support in form field errors
2 parents ca00b0a + c00cd74 commit 1ecf4d4

File tree

8 files changed

+170
-1
lines changed

8 files changed

+170
-1
lines changed
22.5 KB
Loading
3.5 KB
Loading
Loading
Loading
Loading

src/formfield/FormField.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TemplateResult, html, css, LitElement } from 'lit';
22
import { property } from 'lit/decorators.js';
3+
import { renderMarkdown } from '../markdown';
34

45
/**
56
* A small wrapper to display labels and help text in a smartmin style.
@@ -94,7 +95,9 @@ export class FormField extends LitElement {
9495
public render(): TemplateResult {
9596
const errors = !this.hideErrors
9697
? (this.errors || []).map((error: string) => {
97-
return html` <div class="alert-error">${error}</div> `;
98+
return html`
99+
<div class="alert-error">${renderMarkdown(error)}</div>
100+
`;
98101
})
99102
: [];
100103

test/temba-formfield.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { html, fixture, expect } from '@open-wc/testing';
2+
import { FormField } from '../src/formfield/FormField';
3+
import { assertScreenshot, getClip } from './utils.test';
4+
5+
describe('temba-field', () => {
6+
it('renders field with plain text errors', async () => {
7+
const formField: FormField = await fixture(html`
8+
<temba-field
9+
label="Test Field"
10+
name="test"
11+
.errors=${['This is a plain text error', 'Another error message']}
12+
>
13+
<input type="text" />
14+
</temba-field>
15+
`);
16+
17+
await formField.updateComplete;
18+
19+
// Check that errors are rendered
20+
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
21+
expect(errorElements.length).to.equal(2);
22+
expect(errorElements[0].textContent.trim()).to.equal(
23+
'This is a plain text error'
24+
);
25+
expect(errorElements[1].textContent.trim()).to.equal(
26+
'Another error message'
27+
);
28+
29+
await assertScreenshot('formfield/plain-text-errors', getClip(formField));
30+
});
31+
32+
it('renders field with markdown errors', async () => {
33+
const formField: FormField = await fixture(html`
34+
<temba-field
35+
label="Test Field"
36+
name="test"
37+
.errors=${[
38+
'This is **bold** text',
39+
'This has a [link](https://example.com)',
40+
'This is *italic* and **bold** with a [link](https://example.com)'
41+
]}
42+
>
43+
<input type="text" />
44+
</temba-field>
45+
`);
46+
47+
await formField.updateComplete;
48+
49+
// Check that errors are rendered
50+
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
51+
expect(errorElements.length).to.equal(3);
52+
53+
// First error should have bold text
54+
const firstError = errorElements[0];
55+
const boldElement = firstError.querySelector('strong');
56+
expect(boldElement).to.not.be.null;
57+
expect(boldElement.textContent).to.equal('bold');
58+
59+
// Second error should have a link
60+
const secondError = errorElements[1];
61+
const linkElement = secondError.querySelector('a');
62+
expect(linkElement).to.not.be.null;
63+
expect(linkElement.getAttribute('href')).to.equal('https://example.com');
64+
expect(linkElement.textContent).to.equal('link');
65+
66+
// Third error should have both bold, italic, and link
67+
const thirdError = errorElements[2];
68+
const thirdBoldElement = thirdError.querySelector('strong');
69+
const thirdItalicElement = thirdError.querySelector('em');
70+
const thirdLinkElement = thirdError.querySelector('a');
71+
expect(thirdBoldElement).to.not.be.null;
72+
expect(thirdItalicElement).to.not.be.null;
73+
expect(thirdLinkElement).to.not.be.null;
74+
75+
await assertScreenshot('formfield/markdown-errors', getClip(formField));
76+
});
77+
78+
it('renders field without errors', async () => {
79+
const formField: FormField = await fixture(html`
80+
<temba-field label="Test Field" name="test">
81+
<input type="text" />
82+
</temba-field>
83+
`);
84+
85+
await formField.updateComplete;
86+
87+
// Check that no errors are rendered
88+
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
89+
expect(errorElements.length).to.equal(0);
90+
91+
await assertScreenshot('formfield/no-errors', getClip(formField));
92+
});
93+
94+
it('renders in widget-only mode with errors', async () => {
95+
const formField: FormField = await fixture(html`
96+
<temba-field
97+
widget_only
98+
.errors=${['Widget only **error** with [link](https://example.com)']}
99+
>
100+
<input type="text" />
101+
</temba-field>
102+
`);
103+
104+
await formField.updateComplete;
105+
106+
// Check that error is rendered in widget-only mode
107+
const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
108+
expect(errorElements.length).to.equal(1);
109+
110+
const errorElement = errorElements[0];
111+
const boldElement = errorElement.querySelector('strong');
112+
const linkElement = errorElement.querySelector('a');
113+
expect(boldElement).to.not.be.null;
114+
expect(linkElement).to.not.be.null;
115+
116+
await assertScreenshot(
117+
'formfield/widget-only-markdown-errors',
118+
getClip(formField)
119+
);
120+
});
121+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { html, fixture, expect } from '@open-wc/testing';
2+
import { Checkbox } from '../src/checkbox/Checkbox';
3+
import { assertScreenshot, getClip } from './utils.test';
4+
5+
describe('FormElement markdown integration', () => {
6+
it('renders checkbox with markdown errors', async () => {
7+
const checkbox: Checkbox = await fixture(html`
8+
<temba-checkbox
9+
label="Accept Terms"
10+
.errors=${[
11+
'Please read the **terms and conditions** at [this link](https://example.com)',
12+
'This field *requires* acceptance'
13+
]}
14+
></temba-checkbox>
15+
`);
16+
17+
await checkbox.updateComplete;
18+
19+
// Check that errors are rendered with markdown
20+
const errorElements = checkbox.shadowRoot
21+
.querySelectorAll('temba-field')[0]
22+
.shadowRoot.querySelectorAll('.alert-error');
23+
expect(errorElements.length).to.equal(2);
24+
25+
// First error should have bold text and link
26+
const firstError = errorElements[0];
27+
const boldElement = firstError.querySelector('strong');
28+
const linkElement = firstError.querySelector('a');
29+
expect(boldElement).to.not.be.null;
30+
expect(boldElement.textContent).to.equal('terms and conditions');
31+
expect(linkElement).to.not.be.null;
32+
expect(linkElement.getAttribute('href')).to.equal('https://example.com');
33+
34+
// Second error should have italic text
35+
const secondError = errorElements[1];
36+
const italicElement = secondError.querySelector('em');
37+
expect(italicElement).to.not.be.null;
38+
expect(italicElement.textContent).to.equal('requires');
39+
40+
await assertScreenshot(
41+
'integration/checkbox-markdown-errors',
42+
getClip(checkbox)
43+
);
44+
});
45+
});

0 commit comments

Comments
 (0)