aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--TODO.norg6
-rw-r--r--src/eval.ts35
-rw-r--r--src/renderer.ts13
-rw-r--r--tests/fixtures/signup/index.html35
-rw-r--r--tests/signup.spec.ts14
5 files changed, 76 insertions, 27 deletions
diff --git a/TODO.norg b/TODO.norg
index a0e5b50..c055580 100644
--- a/TODO.norg
+++ b/TODO.norg
@@ -8,12 +8,12 @@
- (x) attributes
- (x) `--cssx-text` (and maybe `--cssx-html`?)
- (x) dom tests
+ - (x) `attr` function
+ - (x) `set-attr` should allow specifying id?
+ - (x) `set-attr` + remove attribute?
- ( ) keyboard events
- - ( ) `attr` function
- - ( ) j
- ( ) Evaluate `calc`
- ( ) Additional events
- ( ) Improve error messages
- - ( ) `set-attr` should allow specifying id?
- ( ) `add-class` & `remove-class` should use self if id is not specified?
- ( ) `list` & `tuple` data structures?
diff --git a/src/eval.ts b/src/eval.ts
index 8aeaf09..9f053c2 100644
--- a/src/eval.ts
+++ b/src/eval.ts
@@ -9,7 +9,15 @@ export interface EvalActions {
loadCssx(id: string, url: string): Promise<string>
getVariable(name: string): Promise<string | undefined>
updateVariable(id: string, varName: string, value: string): Promise<void>
- setAttribute(name: string, value: string): Promise<void>
+ getAttribute(
+ id: string | undefined,
+ name: string,
+ ): Promise<string | undefined>
+ setAttribute(
+ id: string | undefined,
+ name: string,
+ value: string,
+ ): Promise<void>
withEvent(fn: (e: any) => void): Promise<void>
getFormData(): Promise<FormData | undefined>
sendRequest(_: {
@@ -88,10 +96,21 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
},
'set-attr': async () => {
- const name = await evalExpr(args[0], actions)
- const value = await evalExpr(args[1], actions)
- if (name && value) {
- actions.setAttribute(name, value)
+ const [id, name, value] =
+ args.length >= 3
+ ? await evalArgs(args, 3, actions)
+ : [undefined, ...(await evalArgs(args, 2, actions))]
+ if (name) {
+ actions.setAttribute(id as string | undefined, name, value ?? '')
+ }
+ },
+ attr: async () => {
+ const [id, name] =
+ args.length >= 2
+ ? await evalArgs(args, 2, actions)
+ : [undefined, await evalExpr(args[0], actions)]
+ if (name) {
+ return actions.getAttribute(id as string | undefined, name)
}
},
'prevent-default': async () => actions.withEvent(e => e.preventDefault()),
@@ -108,3 +127,9 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
_: () => Promise.reject(new Error('not supposed to be here')),
})
+
+export const evalArgs = (
+ args: Array<Expr>,
+ count: number,
+ actions: EvalActions,
+) => Promise.all(args.slice(0, count).map(e => evalExpr(e, actions)))
diff --git a/src/renderer.ts b/src/renderer.ts
index 19cd4d0..1c8184a 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -73,8 +73,17 @@ const getEvalActions = ($element: Element, event: any): EvalActions => ({
$el.style.setProperty(varName, JSON.stringify(value))
}
},
- setAttribute: async (name, value) => {
- $element.setAttribute(name, value)
+ setAttribute: async (id, name, value) => {
+ const $el = id ? document.getElementById(id) : $element
+ if (value) {
+ $el?.setAttribute(name, value)
+ } else {
+ $el?.removeAttribute(name)
+ }
+ },
+ getAttribute: async (id, name) => {
+ const $el = id ? document.getElementById(id) : $element
+ return $el?.getAttribute(name) ?? undefined
},
withEvent: async fn => fn(event),
getFormData: async () =>
diff --git a/tests/fixtures/signup/index.html b/tests/fixtures/signup/index.html
index df69508..690e52c 100644
--- a/tests/fixtures/signup/index.html
+++ b/tests/fixtures/signup/index.html
@@ -11,8 +11,13 @@
content: 'Show form';
}
#show-form-btn {
- --cssx-on-click: add-class(signup-form, 'visible')
- add-class(show-form-btn, 'active');
+ /* prettier-ignore */
+ --cssx-on-click:
+ add-class(signup-form, 'visible')
+ add-class(show-form-btn, 'hidden');
+ }
+ #show-form-btn.hidden {
+ display: none;
}
#signup-form {
@@ -20,22 +25,32 @@
--cssx-children: input#email input#password button#submit-btn;
- --cssx-on-submit: prevent-default() add-class(signup-form, 'submitting')
- delay(0.2s) request('http://example.com/submit/api', POST)
- remove-class(signup-form, 'submitting')
- add-class(signup-form, 'submitted');
+ /* prettier-ignore */
+ --cssx-on-submit: prevent-default()
+ set-attr(submit-btn, 'disabled', 'disabled') /* disable submit button */
+ add-class(signup-form, 'submitting') /* enable state "submitting" */
+ delay(0.2s) /* fake delay */
+ request('http://example.com/submit/api', POST) /* post form data to api */
+ remove-class(signup-form, 'submitting') /* disable state "submitting" */
+ set-attr(submit-btn, 'disabled', '') /* re-enable submit button */
+ add-class(signup-form, 'submitted') /* show user that the form is submitted */
+ ;
}
#signup-form.visible {
display: block;
}
#email {
- --cssx-on-mount: set-attr('name', 'email')
- set-attr('data-testid', 'email');
+ /* prettier-ignore */
+ --cssx-on-mount:
+ set-attr('name', 'email')
+ set-attr('data-testid', attr('name'));
}
#password {
- --cssx-on-mount: set-attr('name', 'password')
- set-attr('data-testid', 'password');
+ /* prettier-ignore */
+ --cssx-on-mount:
+ set-attr('name', 'password')
+ set-attr('data-testid', attr('name'));
}
#submit-btn {
--cssx-on-mount: set-attr('type', 'submit');
diff --git a/tests/signup.spec.ts b/tests/signup.spec.ts
index c2cec0b..beede57 100644
--- a/tests/signup.spec.ts
+++ b/tests/signup.spec.ts
@@ -34,12 +34,10 @@ describe('signup example', () => {
expect($form).not.toBeVisible()
expect($form.nodeName).toBe('FORM')
- // Click and wait for button to get active class (handles delay)
+ // Click and wait for button to get hidden class (handles delay)
fireEvent.click($showFormBtn)
- await waitFor(() =>
- expect($showFormBtn.classList.contains('active')).toBe(true),
- )
+ await waitFor(() => expect($showFormBtn).not.toBeVisible())
expect($form).toBeVisible()
})
@@ -47,9 +45,7 @@ describe('signup example', () => {
beforeEach(async () => {
const $showFormBtn = document.getElementById('show-form-btn')!
fireEvent.click($showFormBtn)
- await waitFor(() =>
- expect($showFormBtn.classList.contains('active')).toBe(true),
- )
+ await waitFor(() => expect($showFormBtn).not.toBeVisible())
const $form = document.getElementById('signup-form')!
expect($form).toBeVisible()
await delay(100) // Wait for mounting
@@ -64,6 +60,8 @@ describe('signup example', () => {
const $password = getByTestId<HTMLInputElement>(document.body, 'password')
$password.value = 'password'
+ await delay(2000)
+
// Submit form
const $submitBtn = getByText(document.body, 'Submit')
fireEvent.click($submitBtn)
@@ -72,12 +70,14 @@ describe('signup example', () => {
await waitFor(() =>
expect($form.classList.contains('submitting')).toBe(true),
)
+ expect($submitBtn).toBeDisabled()
// Should add submitted class to form and remove submitting
await waitFor(() =>
expect($form.classList.contains('submitted')).toBe(true),
)
expect($form.classList.contains('submitting')).toBe(false)
+ expect($submitBtn).toBeEnabled()
// Should have made a request to post form data
expect(window.fetch).toHaveBeenCalledTimes(1)