Skip to content
This repository has been archived by the owner on Jul 28, 2023. It is now read-only.

wp-bind improvements and negation in runtime #213

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions e2e/html/directive-bind.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Directives -- data-wp-bind</title>
<meta itemprop="wp-client-side-navigation" content="active" />
</head>
<body>
<a
data-wp-bind.href="state.url"
data-testid="add missing href at hydration"
></a>

<a
href="/other-url"
data-wp-bind.href="state.url"
data-testid="change href at hydration"
></a>

<input
type="checkbox"
data-wp-bind.checked="state.checked"
data-testid="add missing checked at hydration"
/>

<input
type="checkbox"
checked
data-wp-bind.checked="!state.checked"
data-testid="remove existing checked at hydration"
/>

<button data-testid="toggle" data-wp-on.click="actions.toggle">
Update
</button>

<script src="../../build/e2e/html/directive-bind.js"></script>
<script src="../../build/runtime.js"></script>
<script src="../../build/vendors.js"></script>
</body>
</html>
15 changes: 15 additions & 0 deletions e2e/html/directive-bind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { store } from '../../src/runtime/store';

// State for the store hydration tests.
store({
state: {
url: '/some-url',
checked: true,
},
actions: {
toggle: ({ state }) => {
state.url = '/some-other-url';
state.checked = !state.checked;
},
},
});
56 changes: 56 additions & 0 deletions e2e/specs/directive-bind.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test, expect } from '../tests';

test.describe('data-wp-bind', () => {
test.beforeEach(async ({ goToFile }) => {
await goToFile('directive-bind.html');
});

test('add missing href at hydration', async ({ page }) => {
const el = page.getByTestId('add missing href at hydration');
await expect(el).toHaveAttribute('href', '/some-url');
});

test('change href at hydration', async ({ page }) => {
const el = page.getByTestId('change href at hydration');
await expect(el).toHaveAttribute('href', '/some-url');
});

test('update missing href at hydration', async ({ page }) => {
const el = page.getByTestId('add missing href at hydration');
await expect(el).toHaveAttribute('href', '/some-url');
page.getByTestId('toggle').click();
await expect(el).toHaveAttribute('href', '/some-other-url');
});

test('add missing checked at hydration', async ({ page }) => {
const el = page.getByTestId('add missing checked at hydration');
await expect(el).toHaveAttribute('checked', '');
});

test('remove existing checked at hydration', async ({ page }) => {
const el = page.getByTestId('remove existing checked at hydration');
await expect(el).not.toHaveAttribute('checked', '');
});

test('update existing checked', async ({ page }) => {
const el = page.getByTestId('add missing checked at hydration');
const el2 = page.getByTestId('remove existing checked at hydration');
let checked = await el.evaluate(
(element: HTMLInputElement) => element.checked
);
let checked2 = await el2.evaluate(
(element: HTMLInputElement) => element.checked
);
expect(checked).toBe(true);
expect(checked2).toBe(false);
await page.getByTestId('toggle').click();
checked = await el.evaluate(
(element: HTMLInputElement) => element.checked
);
checked2 = await el2.evaluate(
(element: HTMLInputElement) => element.checked
);
expect(checked).toBe(false);
expect(checked2).toBe(true);
});
});
15 changes: 14 additions & 1 deletion src/runtime/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,22 @@ export default () => {
Object.entries(bind)
.filter((n) => n !== 'default')
.forEach(([attribute, path]) => {
element.props[attribute] = evaluate(path, {
const result = evaluate(path, {
context: contextValue,
});
element.props[attribute] = result;

useEffect(() => {
// This seems necessary because Preact doesn't change the attributes
// on the hydration, so we have to do it manually. It doesn't need
// deps because it only needs to do it the first time.
result === false
? element.ref.current.removeAttribute(attribute)
: element.ref.current.setAttribute(
attribute,
result === true ? '' : result
);
}, []);
});
}
);
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export const component = (name, Comp) => {

// Resolve the path to some property of the store object.
const resolve = (path, context) => {
// If path starts with !, remove it and save a flag.
const isNegative = path[0] === '!' && !!(path = path.slice(1));
let current = { ...store, context };
path.split('.').forEach((p) => (current = current[p]));
return current;
return isNegative ? !current : current;
};

// Generate the evaluate function.
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = [
runtime: './src/runtime',
'e2e/page-1': './e2e/page-1',
'e2e/page-2': './e2e/page-2',
'e2e/html/directive-bind': './e2e/html/directive-bind',
},
output: {
filename: '[name].js',
Expand Down