From ab3147e11d187d9bdbe8f1645221fefc576cb74f Mon Sep 17 00:00:00 2001 From: realaravinth Date: Thu, 6 May 2021 18:16:13 +0530 Subject: [PATCH] add site form validation tests --- templates/panel/sitekey/add/ts/form.ts | 101 ------------- templates/panel/sitekey/add/ts/form/index.ts | 64 +++++++++ .../add/ts/form/validateDescription.test.ts | 37 +++++ .../add/ts/form/validateDescription.ts | 28 ++++ .../add/ts/form/validateDuration.test.ts | 74 ++++++++++ .../sitekey/add/ts/form/validateDuration.ts | 32 +++++ .../add/ts/levels/getLevelFields.test.ts | 27 +++- templates/panel/sitekey/add/ts/setupTests.ts | 136 ++++++++++-------- templates/router.test.ts | 22 ++- templates/router.ts | 60 ++++---- ...{getFromUrl.test.ts => getFormUrl.test.ts} | 9 ++ templates/utils/getFormUrl.ts | 2 +- 12 files changed, 393 insertions(+), 199 deletions(-) delete mode 100644 templates/panel/sitekey/add/ts/form.ts create mode 100644 templates/panel/sitekey/add/ts/form/index.ts create mode 100644 templates/panel/sitekey/add/ts/form/validateDescription.test.ts create mode 100644 templates/panel/sitekey/add/ts/form/validateDescription.ts create mode 100644 templates/panel/sitekey/add/ts/form/validateDuration.test.ts create mode 100644 templates/panel/sitekey/add/ts/form/validateDuration.ts rename templates/utils/{getFromUrl.test.ts => getFormUrl.test.ts} (88%) diff --git a/templates/panel/sitekey/add/ts/form.ts b/templates/panel/sitekey/add/ts/form.ts deleted file mode 100644 index c5425d2d..00000000 --- a/templates/panel/sitekey/add/ts/form.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2021 Aravinth Manivannan - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import {LEVELS} from './levels'; - -import isBlankString from '../../../../utils/isBlankString'; -import getFormUrl from '../../../../utils/getFormUrl'; -import genJsonPayload from '../../../../utils/genJsonPayload'; -import isNumber from '../../../../utils/isNumber'; - -import VIEWS from '../../../../views/v1/routes'; - -const SITE_KEY_FORM_CLASS = 'sitekey-form'; -const FORM = document.querySelector(`.${SITE_KEY_FORM_CLASS}`); -//const FORM_SUBMIT_BUTTON_CLASS = "sitekey-form__submit"; -//const FORM_SUBMIT_BUTTON = document.querySelector(`.${FORM_SUBMIT_BUTTON_CLASS}`); - -const addSubmitEventListener = () => { - FORM.addEventListener('submit', submit, true); -}; - -//const validateLevels = (e: Event) => { -// const numLevels = getNumLevels(); -// // check if levels are unique and are in increasing order; -// // also if they are positive -// // also if level input field is accompanied by a "Add Level" button, -// // it shouldn't be used for validation -// for (let levelNum = 1; levelNum < numLevels; levelNum++) { -// const inputID = CONST.INPUT_ID_WITHOUT_LEVEL + levelNum; -// const inputElement = document.getElementById(inputID); -// const val = inputElement.value; -// const filed = CONST.LABEL_INNER_TEXT_WITHOUT_LEVEL + levelNum; -// isBlankString(val, filed, e); -// } -//}; - -const validateDescription = (e: Event) => { - const inputElement = document.getElementById('description'); - const val = inputElement.value; - const filed = 'Description'; - isBlankString(val, filed, e); - return val; -}; - -const validateDuration = (e: Event) => { - const duartionElement = document.getElementById('duration'); - const duration = parseInt(duartionElement.value); - if (!isNumber(duration) || Number.isNaN(duration)) { - throw new Error('duration can contain nubers only'); - } - - if (duration <= 0) { - throw new Error('duration must be greater than zero'); - } - return duration; -}; - -const submit = async (e: Event) => { - e.preventDefault(); - - const description = validateDescription(e); - const duration = validateDuration(e); - - const formUrl = getFormUrl(FORM); - - const levels = LEVELS.getLevels(); - console.debug(`[form submition]: levels: ${levels}`); - - const payload = { - levels: levels, - duration, - description, - }; - - console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); - - const res = await fetch(formUrl, genJsonPayload(payload)); - if (res.ok) { - alert('success'); - window.location.assign(VIEWS.sitekey); - } else { - const err = await res.json(); - alert(`error: ${err.error}`); - } -}; - -export default addSubmitEventListener; diff --git a/templates/panel/sitekey/add/ts/form/index.ts b/templates/panel/sitekey/add/ts/form/index.ts new file mode 100644 index 00000000..c91bea96 --- /dev/null +++ b/templates/panel/sitekey/add/ts/form/index.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {LEVELS} from '../levels'; + +import getFormUrl from '../../../../../utils/getFormUrl'; +import genJsonPayload from '../../../../../utils/genJsonPayload'; + +import VIEWS from '../../../../../views/v1/routes'; + +import validateDescription from './validateDescription'; +import validateDuration from './validateDuration'; + +const SITE_KEY_FORM_CLASS = 'sitekey-form'; +const FORM = document.querySelector(`.${SITE_KEY_FORM_CLASS}`); + +const addSubmitEventListener = () => { + FORM.addEventListener('submit', submit, true); +}; + +const submit = async (e: Event) => { + e.preventDefault(); + + const description = validateDescription(e); + const duration = validateDuration(e); + + const formUrl = getFormUrl(FORM); + + const levels = LEVELS.getLevels(); + console.debug(`[form submition]: levels: ${levels}`); + + const payload = { + levels: levels, + duration, + description, + }; + + console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); + + const res = await fetch(formUrl, genJsonPayload(payload)); + if (res.ok) { + alert('success'); + window.location.assign(VIEWS.sitekey); + } else { + const err = await res.json(); + alert(`error: ${err.error}`); + } +}; + +export default addSubmitEventListener; diff --git a/templates/panel/sitekey/add/ts/form/validateDescription.test.ts b/templates/panel/sitekey/add/ts/form/validateDescription.test.ts new file mode 100644 index 00000000..67356016 --- /dev/null +++ b/templates/panel/sitekey/add/ts/form/validateDescription.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import validateDescription from './validateDescription'; +import {getAddForm, fillDescription} from '../setupTests'; + +document.body.innerHTML = getAddForm(); + +const emptyErr = "can't be empty"; + +it('validateDescription workds', () => { + try { + const event = new Event('submit'); + validateDescription(event); + } catch (e) { + expect(e.message).toContain(emptyErr); + } + + // fill and validate + fillDescription('testing'); + const event = new Event('submit'); + validateDescription(event); +}); diff --git a/templates/panel/sitekey/add/ts/form/validateDescription.ts b/templates/panel/sitekey/add/ts/form/validateDescription.ts new file mode 100644 index 00000000..3ceccf16 --- /dev/null +++ b/templates/panel/sitekey/add/ts/form/validateDescription.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import isBlankString from '../../../../../utils/isBlankString'; + +const validateDescription = (e: Event) => { + const inputElement = document.getElementById('description'); + const val = inputElement.value; + const filed = 'Description'; + isBlankString(val, filed, e); + return val; +}; + +export default validateDescription; diff --git a/templates/panel/sitekey/add/ts/form/validateDuration.test.ts b/templates/panel/sitekey/add/ts/form/validateDuration.test.ts new file mode 100644 index 00000000..057c56c1 --- /dev/null +++ b/templates/panel/sitekey/add/ts/form/validateDuration.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import isNumber from '../../../../../utils/isNumber'; + +//const validateDuration = (e: Event) => { +// const duartionElement = document.getElementById('duration'); +// const duration = parseInt(duartionElement.value); +// if (!isNumber(duration) || Number.isNaN(duration)) { +// throw new Error('duration can contain nubers only'); +// } +// +// if (duration <= 0) { +// throw new Error('duration must be greater than zero'); +// } +// return duration; +//}; +// +//export default validateDuration; + +import validateDuration from './validateDuration'; +import {getAddForm, fillDuration} from '../setupTests'; + +document.body.innerHTML = getAddForm(); + +const emptyErr = "can't be empty"; +const NaNErr = 'duration can contain nubers only'; +const zeroErr = 'duration must be greater than zero'; + +const duration = 30; + +it('validateDuration workds', () => { + try { + const event = new Event('submit'); + validateDuration(event); + } catch (e) { + expect(e.message).toContain(emptyErr); + } + + // fill string error + try { + fillDuration('testing'); + const event = new Event('submit'); + validateDuration(event); + } catch (e) { + expect(e.message).toContain(NaNErr); + } + + // zero err + try { + fillDuration(0); + const event = new Event('submit'); + validateDuration(event); + } catch (e) { + expect(e.message).toContain(zeroErr); + } + + fillDuration(duration); + const event = new Event('submit'); + expect(validateDuration(event)).toBe(duration); +}); diff --git a/templates/panel/sitekey/add/ts/form/validateDuration.ts b/templates/panel/sitekey/add/ts/form/validateDuration.ts new file mode 100644 index 00000000..8de3e5c0 --- /dev/null +++ b/templates/panel/sitekey/add/ts/form/validateDuration.ts @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import isNumber from '../../../../../utils/isNumber'; + +const validateDuration = (e: Event) => { + const duartionElement = document.getElementById('duration'); + const duration = parseInt(duartionElement.value); + if (!isNumber(duration) || Number.isNaN(duration)) { + throw new Error('duration can contain nubers only'); + } + + if (duration <= 0) { + throw new Error('duration must be greater than zero'); + } + return duration; +}; + +export default validateDuration; diff --git a/templates/panel/sitekey/add/ts/levels/getLevelFields.test.ts b/templates/panel/sitekey/add/ts/levels/getLevelFields.test.ts index 27258ff7..78cf7fbf 100644 --- a/templates/panel/sitekey/add/ts/levels/getLevelFields.test.ts +++ b/templates/panel/sitekey/add/ts/levels/getLevelFields.test.ts @@ -16,14 +16,39 @@ */ import getLevelFields from './getLevelFields'; -import {getAddForm, level1, level2, addLevel} from '../setupTests'; +import { + getAddForm, + level1, + level2, + fillAddLevel, + addLevel, +} from '../setupTests'; document.body.innerHTML = getAddForm(); +const visNumErr = 'visitor can contain nubers only'; +const diffNumErr = 'difficulty can contain nubers only'; + it('get levels fields works', () => { addLevel(level1.visitor_threshold, level1.difficulty_factor); expect(getLevelFields(1)).toEqual(level1); + // NaN visitor + try { + fillAddLevel('test', level2.difficulty_factor); + getLevelFields(2); + } catch (e) { + expect(e.message).toBe(visNumErr); + } + + // Nan difficulty_factor + try { + fillAddLevel(level2.visitor_threshold, 'fooasdads'); + getLevelFields(2); + } catch (e) { + expect(e.message).toBe(diffNumErr); + } + addLevel(level2.visitor_threshold, level2.difficulty_factor); expect(getLevelFields(2)).toEqual(level2); }); diff --git a/templates/panel/sitekey/add/ts/setupTests.ts b/templates/panel/sitekey/add/ts/setupTests.ts index 43a3a9aa..cda0b547 100644 --- a/templates/panel/sitekey/add/ts/setupTests.ts +++ b/templates/panel/sitekey/add/ts/setupTests.ts @@ -19,6 +19,80 @@ import {Level} from './levels/index'; import CONST from './const'; import addLevelButtonAddEventListener from './addLevelButton'; +export const level1: Level = { + difficulty_factor: 200, + visitor_threshold: 500, +}; + +export const level1diffErr: Level = { + difficulty_factor: 100, + visitor_threshold: 600, +}; + +export const level1visErr: Level = { + difficulty_factor: 600, + visitor_threshold: 400, +}; + +export const level2: Level = { + difficulty_factor: 400, + visitor_threshold: 700, +}; + +/** add level to DOM by filling add level form and clicking "Add" button */ +export const addLevel = (visitor: number, diff: number) => { + fillAddLevel(visitor, diff); + const addLevelButton = ( + document.querySelector(`.${CONST.ADD_LEVEL_BUTTON}`) + ); + addLevelButton.click(); +}; + +/** Fill add level form without clicking add button */ +export const fillAddLevel = (visitor: number|string, diff: number|string) => { + addLevelButtonAddEventListener(); + + const level = getNumLevels(); + const visitorField = ( + document.getElementById(`${CONST.VISITOR_WITHOUT_LEVEL}${level}`) + ); + visitorField.value = visitor.toString(); + + const diffField = ( + document.getElementById(`${CONST.DIFFICULTY_WITHOUT_LEVEL}${level}`) + ); + diffField.value = diff.toString(); +}; + +/** Fill add level form without clicking add button */ +export const editLevel = (level: number, visitor?: number, diff?: number) => { + if (visitor !== undefined) { + const visitorField = ( + document.getElementById(`${CONST.VISITOR_WITHOUT_LEVEL}${level}`) + ); + visitorField.value = visitor.toString(); + } + + if (diff !== undefined) { + const diffField = ( + document.getElementById(`${CONST.DIFFICULTY_WITHOUT_LEVEL}${level}`) + ); + diffField.value = diff.toString(); + } +}; + +/** Fill description in add level form */ +export const fillDescription = (description: string) => { + const inputElement = document.getElementById('description'); + inputElement.value = description; +}; + +/** Fill duration in add level form */ +export const fillDuration = (duration: number | string) => { + const inputElement = document.getElementById('duration'); + inputElement.value = duration.toString(); +}; + export const getAddForm = () => `

@@ -89,65 +163,3 @@ export const getAddForm = () => ` `; - -/** add level to DOM by filling add level form and clicking "Add" button */ -export const addLevel = (visitor: number, diff: number) => { - fillAddLevel(visitor, diff); - const addLevelButton = ( - document.querySelector(`.${CONST.ADD_LEVEL_BUTTON}`) - ); - addLevelButton.click(); -}; - -/** Fill add level form without clicking add button */ -export const fillAddLevel = (visitor: number, diff: number) => { - addLevelButtonAddEventListener(); - - const level = getNumLevels(); - const visitorField = ( - document.getElementById(`${CONST.VISITOR_WITHOUT_LEVEL}${level}`) - ); - visitorField.value = visitor.toString(); - - const diffField = ( - document.getElementById(`${CONST.DIFFICULTY_WITHOUT_LEVEL}${level}`) - ); - diffField.value = diff.toString(); -}; - -/** Fill add level form without clicking add button */ -export const editLevel = (level: number, visitor?: number, diff?: number) => { - if (visitor !== undefined) { - const visitorField = ( - document.getElementById(`${CONST.VISITOR_WITHOUT_LEVEL}${level}`) - ); - visitorField.value = visitor.toString(); - } - - if (diff !== undefined) { - const diffField = ( - document.getElementById(`${CONST.DIFFICULTY_WITHOUT_LEVEL}${level}`) - ); - diffField.value = diff.toString(); - } -}; - -export const level1: Level = { - difficulty_factor: 200, - visitor_threshold: 500, -}; - -export const level1diffErr: Level = { - difficulty_factor: 100, - visitor_threshold: 600, -}; - -export const level1visErr: Level = { - difficulty_factor: 600, - visitor_threshold: 400, -}; - -export const level2: Level = { - difficulty_factor: 400, - visitor_threshold: 700, -}; diff --git a/templates/router.test.ts b/templates/router.test.ts index ba9f3ce3..0e03facd 100644 --- a/templates/router.test.ts +++ b/templates/router.test.ts @@ -31,6 +31,10 @@ const settingsRoute = '/settings/'; const settingsResult = 'hello from settings'; const settings = () => (result.result = settingsResult); +const UriExistsErr = 'URI exists'; +const emptyUriErr = 'uri is empty'; +const unregisteredRouteErr = "Route isn't registered"; + const router = new Router(); router.register(panelRoute, panel); router.register(settingsRoute, settings); @@ -43,9 +47,25 @@ it('checks if Router works', () => { router.route(); expect(result.result).toBe(panelResult); + // duplicate URI registration try { router.register(settingsRoute, settings); } catch (e) { - expect(e.message).toBe('URI exists'); + expect(e.message).toBe(UriExistsErr); + } + + // empty URI registration + try { + router.register(' ', settings); + } catch (e) { + expect(e.message).toBe(emptyUriErr); + } + + // routing to unregistered route + try { + window.history.pushState({}, `Page Doesn't Exist`, `/page/doesnt/exist`); + router.route(); + } catch (e) { + expect(e.message).toBe(unregisteredRouteErr); } }); diff --git a/templates/router.ts b/templates/router.ts index e803fdd6..e1d1602c 100644 --- a/templates/router.ts +++ b/templates/router.ts @@ -17,19 +17,16 @@ /** Removes trailing slashed from URI */ const normalizeUri = (uri: string) => { - if (typeof uri == 'string') { - if (uri.trim().length == 0) { - throw new Error('uri is empty'); - } - - let uriLength = uri.length; - if (uri[uriLength - 1] == '/') { - uri = uri.slice(0, uriLength - 1); - } - return uri; - } else { - throw new TypeError(`Only strings are permitted in URI`); + uri = uri.trim(); + if (uri.length == 0) { + throw new Error('uri is empty'); } + + let uriLength = uri.length; + if (uri[uriLength - 1] == '/') { + uri = uri.slice(0, uriLength - 1); + } + return uri; }; /** URI<-> Fn mapping type */ @@ -55,28 +52,17 @@ export class Router { * matches uri * */ register(uri: string, fn: () => void) { - // typechecks - if (uri.trim().length == 0) { - throw new Error('uri is empty'); - } - - if (typeof uri !== 'string') { - throw new TypeError('URI must be a string'); - } - - if (typeof fn !== 'function') { - throw new TypeError('a callback fn must be provided'); - } - uri = normalizeUri(uri); - if (this.routes.find(route => { - if (route.uri == uri) { - return true; - } - })) { - throw new Error('URI exists'); - }; + if ( + this.routes.find(route => { + if (route.uri == uri) { + return true; + } + }) + ) { + throw new Error('URI exists'); + } const route: routeTuple = { uri, @@ -92,11 +78,19 @@ export class Router { route() { const path = normalizeUri(window.location.pathname); + let fn: () => void | undefined; + this.routes.forEach(route => { const pattern = new RegExp(`^${route.uri}$`); if (path.match(pattern)) { - return route.fn(); + fn = route.fn; } }); + + if (fn === undefined) { + throw new Error("Route isn't registered"); + } + + return fn(); } } diff --git a/templates/utils/getFromUrl.test.ts b/templates/utils/getFormUrl.test.ts similarity index 88% rename from templates/utils/getFromUrl.test.ts rename to templates/utils/getFormUrl.test.ts index 81101ca4..f92f597d 100644 --- a/templates/utils/getFromUrl.test.ts +++ b/templates/utils/getFormUrl.test.ts @@ -23,6 +23,8 @@ import {getLoginFormHtml} from '../setUpTests'; const formClassName = 'form__box'; const formURL = '/api/v1/signin'; +const noFormErr = "Can't find form"; + document.body.innerHTML = getLoginFormHtml(); const form = document.querySelector('form'); @@ -37,4 +39,11 @@ it('getFromUrl workds', () => { expect(getFormUrl(form)).toContain(formURL); expect(getFormUrl()).toContain(formURL); + + try { + document.body.innerHTML = formURL; + getFormUrl(); + } catch (e) { + expect(e.message).toContain(noFormErr); + } }); diff --git a/templates/utils/getFormUrl.ts b/templates/utils/getFormUrl.ts index 679680bd..e1221154 100644 --- a/templates/utils/getFormUrl.ts +++ b/templates/utils/getFormUrl.ts @@ -33,7 +33,7 @@ const getFormUrl = (querySelector?: string | HTMLFormElement) => { form = querySelector; } - if (form !== undefined) { + if (form !== undefined && form !== null) { return form.action; } else { throw new Error("Can't find form");