Skip to content

Commit

Permalink
[CODE QUALITY] add tests and update docs (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-west authored Jul 15, 2019
1 parent ef4d31b commit ecb9977
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@ typings/

# for those that use MacOS
.DS_Store

# NPM
package-lock.json
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: node_js
dist: trusty
node_js:
- '10'
install:
- npm install
script:
- npm test
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,40 @@ new PersistentStateRegistry().supportedTags.push('my-custom-input-element');
In this example, `<persistent-state>` will only work if `<my-custom-input-element>`
has a `value` attribute and fires an `input` event when the value changes.

<details>
<summary><strong>Here is an exhaustive list of all the support <code>input</code> types<strong></summary>

- `checkbox`
- `color`
- `date`
- `datetime-local`
- `email`
- `hidden`
- `month`
- `number`
- `password`
- `radio`
- `range`
- `search`
- `tel`
- `text`
- `time`
- `url`
- `week`

</details>

### `<input type="radio">`

Note that with `radio` buttons the name has to be consistent between the elements:
```html
<persistent-state>
<input type="radio" name="some-unique-name" id="o1" value="this"><label for="o1">This</label>
<input type="radio" name="some-unique-name" id="o2" value="that"><label for="o2">That</label>
<input type="radio" name="some-unique-name" id="o3" value="the other"><label for="o3">Or the Other</label>
</persistent-state>
```

## Events

The `PersistentState::ElementInitialized` event is fired when `PersistentState` updates
Expand All @@ -88,4 +122,21 @@ document.addEventListener('PersistentState::ElementInitialized', (e) => {
const { elem } = e.detail;
// handle updated state
});
```

## Resetting Data

If you wish to remove the data stored in the browser for a specific `<persistent-state>` form,
simply query for the web component and call the `reset()` method.

```js
let psForm = document.querySelector('persistent-state');
psForm.reset()
```

Additionally, if you wish to remove all data stored by this package, use the `PersistentStateRegistry`.

```js
// class is a singleton
new PersistentStateRegistry().resetAll();
```
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "persistent-state",
"version": "1.4.4",
"version": "1.5.0",
"description": "A native web component that holds onto the state of input elements during a session and/or between sessions.",
"main": "persistent-state.js",
"authors": [
Expand Down
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"name": "persistent-state",
"version": "1.4.4",
"version": "1.5.0",
"description": "A native web component that holds onto the state of input elements during a session and/or between sessions.",
"main": "persistent-state.js",
"scripts": {
"start": "python3 -m http.server 5000 || python -m SimpleHTTPServer 5000",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest --detectOpenHandles"
},
"repository": "git+https://github.com/kyle-west/persistent-state.git",
"keywords": [
Expand All @@ -18,5 +17,12 @@
"url": "https://github.com/kyle-west/persistent-state/issues"
},
"homepage": "https://github.com/kyle-west/persistent-state#readme",
"private": false
"private": false,
"dependencies": {
"express": "^4.17.1",
"http-server": "^0.11.1",
"jest": "^24.8.0",
"puppeteer": "^1.18.1"
},
"devDependencies": {}
}
153 changes: 153 additions & 0 deletions persistent-state.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// SET UP SERVER AND CLIENT HOOKS
//////////////////////////////////////////////////////////////////////////////////////////////////////////

const puppeteer = require('puppeteer')
const express = require('express')
const port = 8888
let server = null;

beforeAll((done) => {
const testApp = express()

// allow specific static files to be accessed only
testApp.use('/test', express.static('test'))
testApp.use('/persistent-state.js', (req, res) => res.sendFile(__dirname + '/persistent-state.js'))

server = testApp.listen(port, done)
});

let loadTestPage = new Promise((resolve) => {
setTimeout(() => { // give time for the server to load up before we begin the tests
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/test/persistent-state_test.html`, {waitUntil : ['load', 'domcontentloaded']});
resolve({browser, page})
})();
}, 50)
})

afterAll(async (done) => {
await loadTestPage.then(({browser}) => browser.close())
server.close(done)
});

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// RUN TESTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////

test(`The WC loads on the page`, () => {
return loadTestPage.then(async ({ page }) => {
const { element, PersistentStateRegistry } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-1'),
PersistentStateRegistry: window.PersistentStateRegistry
}))
expect(element).toBeDefined();
expect(PersistentStateRegistry).toBeDefined();
})
});

test(`data is saved to the localStorage when type=""`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-2]::INPUT#0'
const VALUE = 'this was written in test 2';

let { element, localBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-2 input'),
localBefore: Object.entries(window.localStorage)
}))

expect(element.__persistent_state__initialized).toBe(true);
expect(localBefore).toStrictEqual([])

await page.waitFor('persistent-state#test-2 input');
await page.type('persistent-state#test-2 input', VALUE);

let { localAfter } = await page.evaluate(() => ({
localAfter: Object.entries(window.localStorage),
}))

expect(localAfter).toStrictEqual([[KEY, VALUE]])
})
});

test(`data is saved to the sessionStorage when type="session"`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-3]::INPUT#0'
const VALUE = 'this was written in test 3';

let { element, sessionBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-3 input'),
sessionBefore: Object.entries(window.sessionStorage)
}))

expect(element.__persistent_state__initialized).toBe(true);
expect(sessionBefore).toStrictEqual([])

await page.waitFor('persistent-state#test-3 input');
await page.type('persistent-state#test-3 input', VALUE);

let { sessionAfter } = await page.evaluate(() => ({
sessionAfter: Object.entries(window.sessionStorage)
}))

expect(sessionAfter).toStrictEqual([[KEY, VALUE]])
})
});

test(`data is saved under a computed key between the key="" attr and the input id`, () => {
return loadTestPage.then(async ({ page }) => {
const TEST3_KEY = 'PersistentStateRegistry::[test-3]::INPUT#0'
const TEST3_VALUE = 'this was written in test 3';
const KEY = 'PersistentStateRegistry::[test-key-attribute]::INPUT#the-input-tag-id'
const VALUE = 'this was written in test 4';

let { element, sessionBefore } = await page.evaluate(() => ({
element: document.querySelector('persistent-state#test-many input'),
sessionBefore: Object.entries(window.sessionStorage)
}))

expect(element.__persistent_state__initialized).toBe(true);
expect(sessionBefore).toStrictEqual([[TEST3_KEY, TEST3_VALUE]])

await page.waitFor('persistent-state#test-many input');
await page.type('persistent-state#test-many input', VALUE);

let { sessionAfter } = await page.evaluate(() => ({
sessionAfter: Object.entries(window.sessionStorage)
}))

expect(sessionAfter).toStrictEqual([[TEST3_KEY, TEST3_VALUE], [KEY, VALUE]])
})
});

test(`data is loaded from sessionStorage by the WC when initialized`, () => {
return loadTestPage.then(async ({ page }) => {
const KEY = 'PersistentStateRegistry::[test-key-attribute]::INPUT#the-input-tag-id'

await page.evaluate(() => {
document.getElementById('root').innerHTML = '';
})

let { beforeInit } = await page.evaluate(() => ({
beforeInit: document.querySelector('persistent-state#test-many input')
}))

expect(beforeInit).toBe(null);

await page.evaluate(() => {
document.getElementById('root').innerHTML = `
<persistent-state id="test-many" key="test-key-attribute" type="session">
<input type="text" id="the-input-tag-id"/>
</persistent-state>
`;
})

let { afterInitValue } = await page.evaluate(() => ({
afterInitValue: document.querySelector('persistent-state#test-many input').value
}))

expect(afterInitValue).toBe('this was written in test 4');
})
});
29 changes: 29 additions & 0 deletions test/persistent-state_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>persistent-state test</title>
<script src="../persistent-state.js"></script>
</head>
<body>
<div id="root">
<persistent-state id="test-1">
<input type="text" />
</persistent-state>

<persistent-state id="test-2">
<input type="text" />
</persistent-state>

<persistent-state id="test-3" type="session">
<input type="text" />
</persistent-state>

<persistent-state id="test-many" key="test-key-attribute" type="session">
<input type="text" id="the-input-tag-id"/>
</persistent-state>
</div>
</body>
</html>

0 comments on commit ecb9977

Please sign in to comment.