diff --git a/.debts b/.debts index 4099407..7273c0f 100644 --- a/.debts +++ b/.debts @@ -1 +1 @@ -23 +25 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..ae7617a --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +purity-modules/ \ No newline at end of file diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 6b9c164..0cce527 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -1,4 +1,5 @@ -trailingComma: 'es5' +trailingComma: es5 tabWidth: 2 semi: false singleQuote: true +arrowParens: avoid diff --git a/README.md b/README.md index ddbc4df..b04872f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Purity ToDo Application +[![Netlify Status](https://api.netlify.com/api/v1/badges/05f29ca9-75bf-4c65-be73-e648421a0ac6/deploy-status)](https://app.netlify.com/sites/reactive-todo-app/deploys) + The app is built using [Purity](https://github.com/tatomyr/purity) reactive store & rendering library and bare native ES modules. The aim is mostly proof of concept. diff --git a/cypress/integration/examples/todo.spec.js b/cypress/integration/examples/todo.spec.js index fa7ab7a..4e306b5 100644 --- a/cypress/integration/examples/todo.spec.js +++ b/cypress/integration/examples/todo.spec.js @@ -15,25 +15,15 @@ context('Basic flow', () => { // a: cy.get('#newTask').type('One') cy.get('form#newTask-form').submit() - cy.get('ol#tasks-list li') - .should('have.length', 1) - .contains('One') + cy.get('ol#tasks-list li').should('have.length', 1).contains('One') cy.wait(0) - cy.get('#newTask') - .should('have.value', '') - .type('Two') + cy.get('#newTask').should('have.value', '').type('Two') cy.get('form#newTask-form').submit() - cy.get('ol#tasks-list li') - .should('have.length', 2) - .contains('Two') + cy.get('ol#tasks-list li').should('have.length', 2).contains('Two') cy.wait(0) - cy.get('#newTask') - .should('have.value', '') - .type('Three') + cy.get('#newTask').should('have.value', '').type('Three') cy.get('form#newTask-form').submit() - cy.get('ol#tasks-list li') - .should('have.length', 3) - .contains('Three') + cy.get('ol#tasks-list li').should('have.length', 3).contains('Three') cy.wait(0) cy.get('#all .counter').should('have.text', '3') @@ -41,17 +31,13 @@ context('Basic flow', () => { cy.get('#completed .counter').should('have.text', '0') // b: - cy.get('ol#tasks-list li:nth-child(2)') - .contains('Two') - .click() + cy.get('ol#tasks-list li:nth-child(2)').contains('Two').click() cy.get('.task-details__controls a:first-child').click() cy.get('ol#tasks-list li').should('have.length', 2) cy.get('#nav-button-completed').click() cy.get('#completed').should('have.class', 'active') - cy.get('ol#tasks-list li') - .should('have.length', 1) - .contains('Two') + cy.get('ol#tasks-list li').should('have.length', 1).contains('Two') cy.wait(0) cy.get('#all .counter').should('have.text', '3') @@ -75,9 +61,7 @@ context('Basic flow', () => { cy.get('#clear').click() cy.wait(0) - cy.get('#newTask') - .should('have.value', '') - .should('not.have.focus') + cy.get('#newTask').should('have.value', '').should('not.have.focus') cy.get('#completed').should('have.class', 'active') cy.get('#all .counter').should('have.text', '3') cy.get('#active .counter').should('have.text', '2') @@ -96,12 +80,8 @@ context('Basic flow', () => { cy.get('#all .counter').should('have.text', '4') cy.get('#active .counter').should('have.text', '3') cy.get('#completed .counter').should('have.text', '1') - cy.get('#newTask') - .should('have.value', '') - .should('not.have.focus') - cy.get('ol#tasks-list li') - .should('have.length', 3) - .contains('Four') + cy.get('#newTask').should('have.value', '').should('not.have.focus') + cy.get('ol#tasks-list li').should('have.length', 3).contains('Four') // e: cy.get('#nav-button-completed').click() @@ -119,9 +99,7 @@ context('Basic flow', () => { cy.get('#active .counter').should('have.text', '2') cy.get('#completed .counter').should('have.text', '1') - cy.get('ol#tasks-list li:last-child') - .contains('One') - .click() + cy.get('ol#tasks-list li:last-child').contains('One').click() cy.get('.task-details__controls a:first-child').click() cy.wait(0) cy.get('#active').should('have.class', 'active') @@ -134,8 +112,23 @@ context('Basic flow', () => { cy.get('#all .counter').should('have.text', '4') cy.get('#active .counter').should('have.text', '2') cy.get('#completed .counter').should('have.text', '2') - cy.get('#newTask') - .should('have.text', '') - .should('not.have.focus') + cy.get('#newTask').should('have.text', '').should('not.have.focus') + }) +}) + +context('Security', () => { + beforeEach(() => { + cy.visit('/#/active') + }) + // TODO: make test fail first + it('sanitizes quotes', async () => { + cy.get('#newTask').type('test" onclick="event.target.value = \'OOPS\'"') + // FIXME: why click doesn't work?? + // TODO: [!MAJOR] investigate why can't add multiple event handlers ! + cy.get('#newTask').click() + cy.get('#newTask').should( + 'have.value', + 'test" onclick="event.target.value = \'OOPS\'"' + ) }) }) diff --git a/src/config/version b/src/config/version index 37ad5c8..a13e7b9 100644 --- a/src/config/version +++ b/src/config/version @@ -1 +1 @@ -9.0.1 +10.0.0 diff --git a/src/hashrouter.js b/src/hashrouter.js index 237b82c..795276d 100644 --- a/src/hashrouter.js +++ b/src/hashrouter.js @@ -3,18 +3,20 @@ let match // FIXME: I believe the common `match` will cause data inferring issues. Work this out. export const router = component => props => component({ ...match, ...props }) -export const Switch = props => { - for (const [path, component] of Object.entries(props)) { +export const getParams = () => match + +export const Switch = routes => { + for (const path in routes) { const params = (path.match(/:(\w+)/g) || []).map(param => param.slice(1)) const matchRe = new RegExp(path.replace(/:\w+/g, '(\\w+[\\w\\-\\.]*)')) const matches = window.location.hash.match(matchRe) + console.log(matches, path, ':', routes[path]) if (!matches) { continue } - const [_, ...args] = window.location.hash.match(matchRe) + const [_, ...args] = matches match = params.reduce(($, param, i) => ({ ...$, [param]: args[i] }), {}) - return (props => component({ ...props, ...match }))() - // return router(component)(props) + return routes[path](match) } } diff --git a/src/index.html b/src/index.html index 80cc921..7189800 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,8 @@ Purity ToDo - - + + diff --git a/src/index.js b/src/index.js index e41bc89..3448b0d 100644 --- a/src/index.js +++ b/src/index.js @@ -7,8 +7,9 @@ mount(App) // Registering service worker // TODO: provide a maintainable DEV/PROD flagging const stage = location.hostname === 'localhost' ? 'DEV' : 'PROD' -console.log(stage, stage !== 'DEV', 'serviceWorker' in navigator) -if (stage !== 'DEV' && 'serviceWorker' in navigator) { +console.log('STAGE:', stage) +console.log('SERVICE WORKER:', 'serviceWorker' in navigator) +if (stage === 'PROD' && 'serviceWorker' in navigator) { navigator.serviceWorker.register('./service-worker.js').then(() => { console.log('Service Worker Registered') }) diff --git a/src/manifest.json b/src/manifest.json index abe0549..d31476d 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -28,7 +28,7 @@ "type": "image/png" } ], - "start_url": "/index.html", + "start_url": "/#/active", "display": "standalone", "background_color": "#5554AB", "theme_color": "#37366E" diff --git a/src/modules.js b/src/modules.js index 8ab016e..727f595 100644 --- a/src/modules.js +++ b/src/modules.js @@ -1,17 +1,13 @@ -export { - createStore, - render, -} from 'https://tatomyr.github.io/purity/core.min.js' -export { registerAsync } from 'https://tatomyr.github.io/purity/utils/register-async.min.js' -export { debounce } from 'https://tatomyr.github.io/purity/lib/debounce.min.js' -// TODO: use minified version after fixing md5.min.js -export { md5 } from 'https://tatomyr.github.io/purity/lib/md5.js' -export { sanitize } from 'https://tatomyr.github.io/purity/lib/sanitize.min.js' +export { createStore, render } from './purity-modules/core.js' +export { registerAsync } from './purity-modules/register-async.js' +export { debounce } from './purity-modules/debounce.js' +export { md5 } from './purity-modules/md5.js' +export { sanitize } from './purity-modules/sanitize.js' // Local: // export { createStore, render } from 'http://192.168.1.3:8081/core.js' // export { registerAsync } from 'http://192.168.1.3:8081/utils/register-async.js' -// export { debounce } from 'http://192.168.1.3:8081/lib/debounce.js' -// export { md5 } from 'http://192.168.1.3:8081/lib/md5.js' -// export { sanitize } from 'http://192.168.1.3:8081/lib/sanitize.js' +// export { debounce } from 'http://192.168.1.3:8081/utils/debounce.js' +// export { md5 } from 'http://192.168.1.3:8081/utils/md5.js' +// export { sanitize } from 'http://192.168.1.3:8081/utils/sanitize.js' diff --git a/src/purity-modules/README.md b/src/purity-modules/README.md new file mode 100644 index 0000000..618d119 --- /dev/null +++ b/src/purity-modules/README.md @@ -0,0 +1,105 @@ +# Useful utils + +A handful of algorithms from different sources for using with **Purity** or without it. + +## MD5 hashing algorithm + +Borrowed [here](http://www.myersdaily.org/joseph/javascript/md5-text.html). + +Usage: + +```javascript +import { md5 } from 'https://tatomyr.github.io/purity/lib/md5.js' + +console.log(md5('some string')) +``` + +## Visibility Sensor + +Taken [here](https://vanillajstoolkit.com/helpers/isinviewport/) + +Usage: + +```javascript +import { trackVisibility } from 'https://tatomyr.github.io/purity/lib/visibility-sensor.js' + +… +trackVisibility($element, isInViewport => { + console.log($element + ' is in viewport:' + isInViewport) +}) +… +``` + + + +## Debounce + +Usage: + +```javascript +import { debounce } from 'https://tatomyr.github.io/purity/lib/debounce.js' + +… +render` + +` +… +``` + +Use a positive `timeout` for triggering the callback on the leading edge and a negative one for triggering the callback on the trailing edge. + +## Delay + +Usage: + +```javascript +import { delay } from 'https://tatomyr.github.io/purity/lib/delay.js' + +… +delay(