* add basic testing for dom

* fix error message

* prefer const when possible

* add test for parsing a field error

* clean up sig

* Parse field specific errors in parseJSONError

* Remove null check

* add function to turn a django error into a map of id and messages

* format tsconfig

* better description

* add norefferrer escaping

* add docs

* Add test for noReferrerLinks

* Shorter test string

* fix linting
This commit is contained in:
Bernhard Posselt 2017-03-17 18:40:15 +01:00 коммит произвёл GitHub
Родитель 9db6a8467a
Коммит 35fb530f58
9 изменённых файлов: 204 добавлений и 6 удалений

Просмотреть файл

@ -11,7 +11,7 @@ tslint=node_modules/.bin/tslint
.PHONY: lint
lint:
$(tslint) $(CURDIR)/nextcloudappstore/core/static/assets/**/*.ts
$(tslint) "$(CURDIR)/nextcloudappstore/core/static/assets/**/*.ts"
$(pycodestyle) $(CURDIR)/nextcloudappstore --exclude=migrations
$(mypy) --silent-imports --disallow-untyped-defs $(CURDIR)/nextcloudappstore/core/api/v1/release
$(mypy) --silent-imports --disallow-untyped-defs $(CURDIR)/nextcloudappstore/core/certificate

Просмотреть файл

@ -11,6 +11,11 @@ type ErrorMessages = {
type DjangoErrors = IDjangoFieldErrors | DjangoGlobalErrors;
/**
* Parses a JSON error from a Django Restframework API
* @param errorJSON
* @returns {ErrorMessages}
*/
export function parseJSONError(errorJSON: DjangoErrors): ErrorMessages {
const result: ErrorMessages = {
fields: {},
@ -19,7 +24,31 @@ export function parseJSONError(errorJSON: DjangoErrors): ErrorMessages {
if (Array.isArray(errorJSON)) {
result.global = errorJSON;
} else if (typeof errorJSON === 'object') {
result.fields = errorJSON;
}
return result;
}
type SelectorPairs = Map<string, string[]>;
/**
* Turns a parsed JSON error into a map of element id's and array of error msgs
* @param messages
* @returns {Map<string, string[]>}
*/
export function toIdErrorMap(messages: ErrorMessages): SelectorPairs {
const result = new Map<string, string[]>();
result.set('global_error', messages.global);
const fields = messages.fields;
Object.keys(fields)
.map((key) => [key, fields[key]])
.forEach(([key, value]: [string, string[]]) => {
result.set('id_' + key, value);
});
return result;
}

Просмотреть файл

@ -1,4 +1,4 @@
import {parseJSONError} from './Form';
import {parseJSONError, toIdErrorMap} from './Form';
describe('Testing the window error result parser', () => {
@ -10,4 +10,65 @@ describe('Testing the window error result parser', () => {
});
});
it('should parse a field error', () => {
expect(parseJSONError({
signature: [
'Field must not be empty',
'Field must be a signature',
],
})).toEqual({
fields: {
signature: [
'Field must not be empty',
'Field must be a signature',
],
},
global: [],
});
});
it('should turn a JSON field error to an id and message map', () => {
const expected = new Map<string, string[]>();
expected.set('id_signature', [
'Field must not be empty',
'Field must be a signature',
]);
expected.set('id_test', [
'Field must not be empty',
]);
const result = toIdErrorMap(parseJSONError({
signature: [
'Field must not be empty',
'Field must be a signature',
],
test: [
'Field must not be empty',
],
}));
expect(result.size).toEqual(3);
expect(result.get('id_signature'))
.toEqual(expected.get('id_signature'));
expect(result.get('id_test'))
.toEqual(expected.get('id_test'));
expect(result.get('global_error'))
.toEqual([]);
});
it('should turn a JSON field error to an id and message map', () => {
const expected = new Map<string, string[]>();
expected.set('global_error', [
'Field must not be empty',
'Field must be a signature',
]);
const result = toIdErrorMap(parseJSONError([
'Field must not be empty',
'Field must be a signature',
]));
expect(result.size).toEqual(1);
expect(result.get('global_error'))
.toEqual(expected.get('global_error'));
});
});

Просмотреть файл

@ -14,12 +14,71 @@ export function queryAll(selector: string): Element[] {
return Array.from(window.document.querySelectorAll(selector));
}
/**
* Parses the header for a meta tag with a certain name and returns the content
* @param name
* @returns {any}
*/
export function getMetaValue(name: string): string | null {
const result = query(`meta[name="${name}]"`);
if (result === null) {
const result = query(`meta[name="${name}"]`);
if (result === null || !(result instanceof HTMLMetaElement)) {
return null;
} else {
const metaTag = result as HTMLMetaElement;
return metaTag.content;
}
}
/**
* Remove elements from the dom
* @param selector selector that matches the elements
*/
export function removeElements(selector: string): void {
queryAll(selector)
.forEach((elem) => elem.remove());
}
/**
* Turns a string into html if possible
* @param html must have a root element
* @returns {Element}
*/
export function toHtml(html: string): Element | null {
const tmp = document.createElement('div');
tmp.innerHTML = html;
return tmp.querySelector('*');
}
/**
* Appends an HTML string to a parent element
* @param parentSelector selector to find the parent
* @param html Html string, must have a root element
* @throws Error if either parent or Html is invalid
* @returns {Element}
*/
export function appendHtml(parentSelector: string, html: string): Element {
const parent = query(parentSelector);
const child = toHtml(html);
if (parent === null || child === null) {
throw new Error('Parent or child are null');
} else {
parent.appendChild(child);
}
return child;
}
/**
* Inserts html into the dom, executes a function and then removes the inserted
* element again
* @param parentSelector selector where to insert the html
* @param html actual html
* @param callback function that is executed in between
*/
export function testDom(parentSelector: string, html: string,
callback: (elem: Element) => void): void {
const elem = appendHtml(parentSelector, html);
callback(elem);
elem.remove();
}

Просмотреть файл

@ -0,0 +1,15 @@
import {getMetaValue, testDom} from './Facades';
describe('DOM Facades', () => {
it('should find no meta values', () => {
const tpl = `<meta name="tests" content="value">`;
testDom('head', tpl, () => expect(getMetaValue('test')).toBeNull());
});
it('should find meta values', () => {
const tpl = `<meta name="test" content="value">`;
testDom('head', tpl, () => expect(getMetaValue('test')).toBe('value'));
});
});

Просмотреть файл

@ -1,5 +1,23 @@
/**
* Escapes a string for including it in HTML
* @param text
* @returns {string}
*/
export function escapeHtml(text: string): string {
const div = window.document.createElement('div');
div.appendChild(window.document.createTextNode(text));
return div.innerHTML;
}
/**
* Adds rel="nooopener noreferrer" to all <a> tags in an html string
* @param html
* @returns {string}
*/
export function noReferrerLinks(html: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
Array.from(doc.getElementsByTagName('a'))
.forEach((link) => link.rel = 'noopener noreferrer');
return doc.body.innerHTML;
}

Просмотреть файл

@ -0,0 +1,12 @@
import {noReferrerLinks} from './Templating';
describe('HTML templating utilities', () => {
it('should add rel="noreferrer noopener"', () => {
const html = 'Lorem <a href="#">ipsum </a> sit';
const expected =
'Lorem <a href="#" rel="noopener noreferrer">ipsum </a> sit';
expect(noReferrerLinks(html)).toEqual(expected);
});
});

Просмотреть файл

@ -9,7 +9,10 @@
"noImplicitReturns": true,
"noImplicitThis": true,
"outDir": "nextcloudappstore/core/static/public",
"lib": [ "es2015", "dom" ]
"lib": [
"es2015",
"dom"
]
},
"include": [
"nextcloudappstore/core/static/assets/app/**/*"

Просмотреть файл

@ -5,6 +5,7 @@
"no-duplicate-variable": true,
"indent": [true, "spaces"],
"quotemark": [true, "single"],
"max-line-length": [true, 80]
"max-line-length": [true, 80],
"prefer-const": true
}
}