зеркало из https://github.com/nextcloud/appstore.git
Add basic testing for dom (#448)
* 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:
Родитель
9db6a8467a
Коммит
35fb530f58
2
Makefile
2
Makefile
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче