diff --git a/backend/src/main/resources/static/favicon.ico b/backend/src/main/resources/static/favicon.ico new file mode 100644 index 000000000..9c1f419d3 Binary files /dev/null and b/backend/src/main/resources/static/favicon.ico differ diff --git a/ui/package-lock.json b/ui/package-lock.json index 77fd5a08a..ab86d547d 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -2205,9 +2205,9 @@ } }, "@testing-library/dom": { - "version": "7.30.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.4.tgz", - "integrity": "sha512-GObDVMaI4ARrZEXaRy4moolNAxWPKvEYNV/fa6Uc2eAzR/t4otS6A7EhrntPBIQLeehL9DbVhscvvv7gd6hWqA==", + "version": "7.31.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", + "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", @@ -2215,7 +2215,7 @@ "@types/aria-query": "^4.2.0", "aria-query": "^4.2.2", "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", + "dom-accessibility-api": "^0.5.6", "lz-string": "^1.4.4", "pretty-format": "^26.6.2" }, @@ -2233,9 +2233,9 @@ } }, "@testing-library/jest-dom": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz", - "integrity": "sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.13.0.tgz", + "integrity": "sha512-+jXXTn8GjRnZkJfzG/tqK/2Q7dGlBInR412WE7Aml7CT3wdSpx5dMQC0HOwVQoZ3cNTmQUy8fCVGUV/Zhoyvcw==", "dev": true, "requires": { "@babel/runtime": "^7.9.2", @@ -2249,9 +2249,9 @@ } }, "@testing-library/react": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz", - "integrity": "sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", + "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -2408,9 +2408,9 @@ } }, "@types/jest": { - "version": "26.0.22", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", - "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "version": "26.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", + "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", "dev": true, "requires": { "jest-diff": "^26.0.0", @@ -2520,9 +2520,9 @@ "dev": true }, "@types/testing-library__jest-dom": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", - "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.13.0.tgz", + "integrity": "sha512-tfjY4Fzzwg1wSU31MWaIH8rzJ2WPtQtUNnZ0wcZwzzhWWRa63Jb1fB7tl79fGX7PUL/4ZHjKs+tcY5BZ8nfNyg==", "dev": true, "requires": { "@types/jest": "*" @@ -5642,9 +5642,9 @@ } }, "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz", + "integrity": "sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==", "dev": true }, "dom-converter": { diff --git a/ui/package.json b/ui/package.json index 45deba74d..1046146a3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -31,12 +31,12 @@ "web-vitals": "^1.0.1" }, "devDependencies": { - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", + "@testing-library/jest-dom": "^5.13.0", + "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.1.10", + "http-proxy-middleware": "^1.2.0", "react-scripts": "4.0.3", - "sass": "1.32.11", - "http-proxy-middleware": "^1.2.0" + "sass": "1.32.11" }, "scripts": { "start": "react-scripts start", @@ -48,6 +48,12 @@ "copy:static": "node ./build", "copy": "npm run copy:static && npm run build:static" }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{js,jsx}", + "!src/test/**/*.*" + ] + }, "eslintConfig": { "extends": [ "react-app", diff --git a/ui/src/app/core/components/FormattedDate.js b/ui/src/app/core/components/FormattedDate.js index 5e84ba7df..e9298af73 100644 --- a/ui/src/app/core/components/FormattedDate.js +++ b/ui/src/app/core/components/FormattedDate.js @@ -1,8 +1,10 @@ import React from 'react'; import { format, parseISO } from 'date-fns'; -export default function FormattedDate ({ date, time = false }) { +export function FormattedDate ({ date, time = false }) { const formatted = React.useMemo(() => format(parseISO(date), `MMM d, Y${time ? ' HH:mm:ss' : ''}`), [date, time]); return (<>{ formatted }); -} \ No newline at end of file +} + +export default FormattedDate; \ No newline at end of file diff --git a/ui/src/app/core/components/FormattedDate.test.js b/ui/src/app/core/components/FormattedDate.test.js new file mode 100644 index 000000000..a013c93ea --- /dev/null +++ b/ui/src/app/core/components/FormattedDate.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { FormattedDate } from './FormattedDate'; + +it('should display a formatted date', () => { + render(); + expect(screen.getByText('Jun 10, 2021')).toBeInTheDocument(); +}); \ No newline at end of file diff --git a/ui/src/app/core/components/VersionInfo.js b/ui/src/app/core/components/VersionInfo.js index f9079037a..ab1fff8d5 100644 --- a/ui/src/app/core/components/VersionInfo.js +++ b/ui/src/app/core/components/VersionInfo.js @@ -9,7 +9,7 @@ const formatter = v => v && v.build ? `${v.build.version}-${v.git.commit.id}` : const year = new Date().getFullYear(); const params = { year }; -export default function VersionInfo () { +export function VersionInfo () { const { data = {} } = useFetch('/actuator/info', {}, []); @@ -26,4 +26,6 @@ export default function VersionInfo () { Copyright & copy; Internet2

); -} \ No newline at end of file +} + +export default VersionInfo; \ No newline at end of file diff --git a/ui/src/app/core/components/VersionInfo.test.js b/ui/src/app/core/components/VersionInfo.test.js new file mode 100644 index 000000000..28a8a6b47 --- /dev/null +++ b/ui/src/app/core/components/VersionInfo.test.js @@ -0,0 +1,21 @@ +import React from 'react'; + +import { render, screen } from '@testing-library/react'; + +import { VersionInfo } from './VersionInfo'; + +const data = { "git": { "branch": "react", "commit": { "id": "634f1a4", "time": "2021-06-10T19:00:29Z" } }, "build": { "artifact": "shibui", "name": "backend", "time": "2021-06-10T19:26:14.478Z", "version": "2.0.0-SNAPSHOT", "group": "edu.internet2.tier.shibboleth.admin.ui" } }; + +jest.mock('../../i18n/components/translate', () => { + return 'span'; +}) + +jest.mock('use-http', () => { + return () => ({ data }); +}); + +it('should display formatted version information', () => { + + render(); + expect(screen.getByText('2.0.0-SNAPSHOT-634f1a4', { exact: false })).toBeInTheDocument(); +}); \ No newline at end of file diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js index 5294abaf5..74e1b6452 100644 --- a/ui/src/app/dashboard/view/Dashboard.js +++ b/ui/src/app/dashboard/view/Dashboard.js @@ -7,7 +7,6 @@ import { NavLink } from 'react-router-dom'; import Translate from '../../i18n/components/translate'; import { AdminRoute } from '../../core/components/AdminRoute'; -import './Dashboard.scss'; import { SourcesTab } from './SourcesTab'; import { ProvidersTab } from './ProvidersTab'; import { AdminTab } from './AdminTab'; diff --git a/ui/src/app/dashboard/view/Dashboard.scss b/ui/src/app/dashboard/view/Dashboard.scss deleted file mode 100644 index 74201a46e..000000000 --- a/ui/src/app/dashboard/view/Dashboard.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '../../../theme/variables.scss'; - diff --git a/ui/src/app/i18n/hooks.js b/ui/src/app/i18n/hooks.js index 35b867de8..1d447c687 100644 --- a/ui/src/app/i18n/hooks.js +++ b/ui/src/app/i18n/hooks.js @@ -12,14 +12,6 @@ export function getMessage(value, messages) { return messages.hasOwnProperty(value) ? messages[value] : (Object.keys(messages).length ? value : ''); } -export function useCurrentLanguage () { - -} - -export function useCurrentLocale () { - -} - export function useTranslation (value, interpolated = {}) { const messages = useContext(I18nContext); const val = getMessage(value, messages); diff --git a/ui/src/app/i18n/hooks.test.js b/ui/src/app/i18n/hooks.test.js new file mode 100644 index 000000000..23f41c145 --- /dev/null +++ b/ui/src/app/i18n/hooks.test.js @@ -0,0 +1,71 @@ +import React from 'react'; + +import { translate, getMessage, useTranslation, useTranslator } from './hooks'; + +const msgs = { foo: 'bar { baz }' }; + +describe('getMessage', () => { + it('should return the expected message based on the key', () => { + expect(getMessage('foo', msgs)).toEqual('bar { baz }'); + }); +}) + +describe('translate', () => { + it('should translate the provided message', () => { + const msg = getMessage('foo', msgs); + expect(translate(msg, { baz: 'baz' })).toEqual('bar baz'); + }); + + it('should return provided value if no interpolation strings are passed', () => { + expect(translate('foo', {})).toEqual('foo'); + }); + + it('should return an empty string if messages are empty', () => { + expect(getMessage('foo', {})).toEqual(''); + }); +}); + +describe('useTranslation hook', () => { + let realUseContext; + let useContextMock; + beforeEach(() => { + realUseContext = React.useContext; + useContextMock = React.useContext = jest.fn(); + }); + // Cleanup mock + afterEach(() => { + React.useContext = realUseContext; + }); + + test("mock hook", () => { + useContextMock.mockReturnValue(msgs); + + expect(useTranslation('foo', { baz: 'baz' })).toBe('bar baz'); + }); + //jest.mock('useContext') + + it('should translate the provided message', () => { + const msg = getMessage('foo', { foo: 'bar { baz }' }); + expect(translate(msg, { baz: 'baz' })).toEqual('bar baz'); + }); +}); + +describe('useTranslator hook', () => { + let realUseContext; + let useContextMock; + beforeEach(() => { + realUseContext = React.useContext; + useContextMock = React.useContext = jest.fn(); + }); + // Cleanup mock + afterEach(() => { + React.useContext = realUseContext; + }); + + test("mock hook", () => { + useContextMock.mockReturnValue(msgs); + const translator = useTranslator(); + + expect(translator('foo', { baz: 'baz' })).toBe('bar baz'); + }); +}); \ No newline at end of file diff --git a/ui/src/app/metadata/domain/index.test.js b/ui/src/app/metadata/domain/index.test.js new file mode 100644 index 000000000..38438a14f --- /dev/null +++ b/ui/src/app/metadata/domain/index.test.js @@ -0,0 +1,12 @@ +import { NameIDFilterEditor } from './filter/NameIdFilterDefinition'; +import { getDefinition } from './index'; +import { FileSystemMetadataProviderEditor } from './provider/FileSystemMetadataProviderDefinition'; +import { SourceEditor } from './source/SourceDefinition'; + +describe('getDefinitions method', () => { + it('should retrieve the definition', () => { + expect(getDefinition('source')).toBe(SourceEditor); + expect(getDefinition('NameIDFormat')).toBe(NameIDFilterEditor); + expect(getDefinition('FilesystemMetadataResolver')).toBe(FileSystemMetadataProviderEditor); + }); +}); \ No newline at end of file diff --git a/ui/src/app/metadata/editor/MetadataEditorForm.js b/ui/src/app/metadata/editor/MetadataEditorForm.js index f8e9bd29c..f1288fd16 100644 --- a/ui/src/app/metadata/editor/MetadataEditorForm.js +++ b/ui/src/app/metadata/editor/MetadataEditorForm.js @@ -34,6 +34,8 @@ export function MetadataEditorForm({ metadata, definition, schema, current, onCh onChange(definition.bindings ? { ...form, formData: definition.bindings(data, form.formData) }: form); }; + console.log(uiSchema); + return ( <> {step.locked &&
diff --git a/ui/src/app/metadata/hoc/MetadataSchema.test.js b/ui/src/app/metadata/hoc/MetadataSchema.test.js deleted file mode 100644 index e8164a2cc..000000000 --- a/ui/src/app/metadata/hoc/MetadataSchema.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; -import ReactDOM from 'react-dom'; -import { render, screen } from "@testing-library/react"; -import { act } from 'react-dom/test-utils'; -import { MetadataSchema, useMetadataDefinitionContext } from './MetadataSchema'; - -let container; -beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); -}); - -afterEach(() => { - document.body.removeChild(container); - container = null; -}); - -const Tester = () => { - const definition = useMetadataDefinitionContext(); - - return ( - <> - <>{definition.type} - - ); -} - - -xdescribe("", () => { - /*xact(() => { - ReactDOM.render( - - - , - container - ); - });*/ - - xdescribe("definition context", () => { - it("should provide the type of the definition", () => { - expect(screen.getByText('@MetadataProvider')).toBeTruthy(); - }); - }); -}); \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/api.test.js b/ui/src/app/metadata/hooks/api.test.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/src/app/metadata/hooks/schema.js b/ui/src/app/metadata/hooks/schema.js index 2aeb62c4f..ecde47f05 100644 --- a/ui/src/app/metadata/hooks/schema.js +++ b/ui/src/app/metadata/hooks/schema.js @@ -1,14 +1,12 @@ import React from 'react'; import { useIsAdmin } from '../../core/user/UserContext'; -const fillInRootProperties = (keys, ui) => { - return keys.reduce((sch, key, idx) => { - if (!sch.hasOwnProperty(key)) { - sch[key] = {}; - } - return sch; - }, ui); -} +export const fillInRootProperties = (keys, ui) => keys.reduce((sch, key, idx) => { + if (!sch.hasOwnProperty(key)) { + sch[key] = {}; + } + return sch; +}, ui); export function useUiSchema(definition, schema, current, locked = true) { @@ -17,23 +15,21 @@ export function useUiSchema(definition, schema, current, locked = true) { const step = React.useMemo(() => definition ? definition.steps.find(step => step.id === current) : {fields: []}, [definition, current]); const filled = React.useMemo(() => fillInRootProperties(schemaKeys, ui), [schemaKeys, ui]); - const mapped = React.useMemo(() => { - return Object.keys(filled).reduce((sch, key) => { - const obj = { ...filled[key] }; - if (step.fields.indexOf(key) === -1) { - obj["ui:widget"] = 'hidden'; - } - sch[key] = obj; - return sch; - }, {}) - }, [filled, step]); - - const isLocked = React.useMemo(() => { - return { + const mapped = React.useMemo(() => Object.keys(filled).reduce((sch, key) => { + const obj = { ...filled[key] }; + if (step.fields.indexOf(key) === -1) { + obj["ui:widget"] = 'hidden'; + } + sch[key] = obj; + return sch; + }, {}), [filled, step]); + + const isLocked = React.useMemo(() => ( + { ...mapped, 'ui:disabled': locked && step.locked ? true : false - }; - }, [mapped, step.locked, locked]); + } + ), [mapped, step.locked, locked]); const isAdmin = useIsAdmin(); diff --git a/ui/src/app/metadata/hooks/schema.test.js b/ui/src/app/metadata/hooks/schema.test.js index e69de29bb..1679ba221 100644 --- a/ui/src/app/metadata/hooks/schema.test.js +++ b/ui/src/app/metadata/hooks/schema.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { useUiSchema } from './schema'; + +import { SourceEditor } from '../domain/source/SourceDefinition'; + +import jsonSchema from '../../../testing/sourceSchema'; +import uiSchemaResult from '../../../testing/uiSchema'; + +import { useIsAdmin } from '../../core/user/UserContext'; +jest.mock('../../core/user/UserContext'); + +describe('useUiSchema', () => { + let realUseMemo; + let useMemoMock; + beforeEach(() => { + realUseMemo = React.useMemo; + useMemoMock = React.useMemo = (cb) => cb(); + }); + // Cleanup mock + afterEach(() => { + React.useMemo = realUseMemo; + }); + + test('should return a parsed ui schema', () => { + useIsAdmin.mockResolvedValue(false); + + const { uiSchema } = useUiSchema(SourceEditor, jsonSchema, 'common') + expect(uiSchema).toEqual(uiSchemaResult); + }) + +}); \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/utility.js b/ui/src/app/metadata/hooks/utility.js index d8279ec07..8b8ab5961 100644 --- a/ui/src/app/metadata/hooks/utility.js +++ b/ui/src/app/metadata/hooks/utility.js @@ -1,4 +1,4 @@ -function getDefinition(path, definitions) { +export function getDefinition(path, definitions) { let def = path.split('/').pop(); return definitions[def]; } diff --git a/ui/src/app/metadata/hooks/utility.test.js b/ui/src/app/metadata/hooks/utility.test.js index e69de29bb..9faaebec2 100644 --- a/ui/src/app/metadata/hooks/utility.test.js +++ b/ui/src/app/metadata/hooks/utility.test.js @@ -0,0 +1,61 @@ +import { getStepProperties, getDefinition, getPropertyItemSchema, getStepProperty } from './utility'; +import SCHEMA from '../../../testing/simpleSchema'; + +describe('domain utility functions', () => { + describe('getStepProperties function', () => { + it('should return an empty array of schema or schema.properties is not defined', () => { + expect(getStepProperties(null, {})).toEqual([]); + expect(getStepProperties({}, {})).toEqual([]); + }); + + it('should return a formatted list of properties', () => { + expect(getStepProperties(SCHEMA, {}).length).toBe(4); + }); + }); + + describe('getDefinitions method', () => { + it('should retrieve the definitions from the json schema', () => { + const definition = { + id: 'foo', + title: 'bar', + description: 'baz', + type: 'string' + }; + expect(getDefinition('/foo/bar', { bar: definition })).toBe(definition); + }); + }); + + describe('getPropertyItemSchema method', () => { + it('should return null if no items are provided', () => { + expect(getPropertyItemSchema(null, SCHEMA.definitions)).toBeNull(); + }); + it('should retrieve the definitions from the items schema', () => { + expect(getPropertyItemSchema({ $ref: 'description' }, SCHEMA.definitions)).toBe(SCHEMA.definitions.description); + }); + it('should return the item itself if no $ref', () => { + let item = {}; + expect(getPropertyItemSchema(item, SCHEMA.definitions)).toBe(item); + }); + }); + + describe('getStepProperty method', () => { + const model = { + name: 'foo', + type: 'bar', + description: 'baz' + }; + it('should return null if no items are provided', () => { + expect(getStepProperty(null, null, SCHEMA.definitions)).toBeNull(); + }); + + it('should retrieve the property $ref definition if available', () => { + const property = getStepProperty( + { $ref: 'description' }, + model, + SCHEMA.definitions + ); + expect(property.type).toBe('string'); + }); + }); +}); + diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js index f5f96e21d..8f2609b7b 100644 --- a/ui/src/setupTests.js +++ b/ui/src/setupTests.js @@ -3,39 +3,3 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; - -const localStorageMock = { - getItem: jest.fn(), - setItem: jest.fn(), - removeItem: jest.fn(), - clear: jest.fn(), -}; -global.localStorage = localStorageMock; -/* -const documentCookieMock = { - cookies: '', - - get cookie() { - return this.cookies; - }, - - set cookie(cookieValue) { - const cookies = this.cookies.split(' '); - const cookieName = cookieValue.split('=').shift(); - const cookieNameLength = cookieName.length; - let cookieIndex = -1; - cookies.forEach((value, index) => { - if (`${value.substr(0, cookieNameLength)}=` === `${cookieName}=`) { - cookieIndex = index; - } - }); - if (cookieIndex > -1) { - cookies[cookieIndex] = `${cookieValue};`; - } else { - cookies.push(`${cookieValue};`); - } - this.cookies = cookies.join(' ').trim(); - }, -}; - -global.document.cookie = documentCookieMock;*/ \ No newline at end of file diff --git a/ui/src/testing/simpleSchema.js b/ui/src/testing/simpleSchema.js new file mode 100644 index 000000000..43457ca7c --- /dev/null +++ b/ui/src/testing/simpleSchema.js @@ -0,0 +1,131 @@ +export const SCHEMA = { + 'title': 'MetadataResolver', + 'type': 'object', + 'widget': { + 'id': 'fieldset' + }, + 'properties': { + 'name': { + 'title': 'Metadata Provider Name (Dashboard Display Only)', + 'description': 'Metadata Provider Name (Dashboard Display Only)', + 'type': 'string', + 'widget': { + 'id': 'string', + 'help': 'Must be unique.' + } + }, + '@type': { + 'title': 'Metadata Provider Type', + 'description': 'Metadata Provider Type', + 'ui:placeholder': 'Select a metadata provider type', + 'type': 'string', + 'widget': { + 'id': 'select' + }, + 'oneOf': [ + { + 'enum': [ + 'FileBackedHttpMetadataResolver' + ], + 'description': 'FileBackedHttpMetadataProvider' + } + ] + }, + 'list': { + 'title': 'label.retained-roles', + 'description': 'tooltip.retained-roles', + 'type': 'array', + 'items': { + 'widget': { + 'id': 'select' + }, + 'type': 'string', + 'oneOf': [ + { + 'enum': [ + 'SPSSODescriptor' + ], + 'description': 'value.spdescriptor' + }, + { + 'enum': [ + 'AttributeAuthorityDescriptor' + ], + 'description': 'value.attr-auth-descriptor' + } + ] + } + }, + 'formatFilterTarget': { + 'title': 'label.search-criteria', + 'description': 'tooltip.search-criteria', + 'type': 'object', + 'widget': { + 'id': 'filter-target', + 'target': 'formatFilterTargetType' + }, + 'properties': { + 'formatFilterTargetType': { + 'title': '', + 'type': 'string', + 'default': 'ENTITY', + 'oneOf': [ + { + 'enum': [ + 'ENTITY' + ], + 'description': 'value.entity-id' + }, + { + 'enum': [ + 'REGEX' + ], + 'description': 'value.regex' + }, + { + 'enum': [ + 'CONDITION_SCRIPT' + ], + 'description': 'value.script' + } + ] + }, + 'value': { + 'type': 'array', + 'minItems': 1, + 'uniqueItems': true, + 'items': { + 'type': 'string' + } + } + }, + 'required': [ + 'value', + 'nameIdFormatFilterTargetType' + ] + } + }, + 'required': [ + 'name', + '@type' + ], + 'fieldsets': [ + { + 'type': 'section', + 'fields': [ + 'name', + '@type' + ] + } + ], + 'definitions': { + 'description': { + 'title': 'Description', + 'description': 'A description of the object', + 'type': 'string', + 'widget': 'string' + } + } +}; + +export default SCHEMA; \ No newline at end of file diff --git a/ui/src/testing/sourceSchema.js b/ui/src/testing/sourceSchema.js new file mode 100644 index 000000000..93262c5f4 --- /dev/null +++ b/ui/src/testing/sourceSchema.js @@ -0,0 +1,3 @@ +const SCHEMA = { "type": "object", "required": ["serviceProviderName", "entityId"], "properties": { "serviceProviderName": { "title": "label.service-provider-name", "description": "tooltip.service-provider-name", "type": "string", "minLength": 1, "maxLength": 255 }, "entityId": { "title": "label.entity-id", "description": "tooltip.entity-id", "type": "string", "minLength": 1, "maxLength": 255 }, "serviceEnabled": { "title": "label.enable-this-service", "description": "tooltip.enable-this-service-upon-saving", "type": "boolean", "default": false }, "organization": { "$ref": "#/definitions/Organization" }, "contacts": { "title": "label.contact-information", "description": "tooltip.contact-information", "type": "array", "items": { "$ref": "#/definitions/Contact" } }, "mdui": { "$ref": "#/definitions/MDUI" }, "securityInfo": { "type": "object", "widget": { "id": "fieldset" }, "dependencies": { "authenticationRequestsSigned": { "oneOf": [{ "properties": { "authenticationRequestsSigned": { "enum": [true] }, "x509Certificates": { "minItems": 1 } } }, { "properties": { "authenticationRequestsSigned": { "enum": [false] }, "x509Certificates": { "minItems": 0 } } }] } }, "properties": { "x509CertificateAvailable": { "type": "boolean", "default": true }, "authenticationRequestsSigned": { "title": "label.authentication-requests-signed", "description": "tooltip.authentication-requests-signed", "type": "boolean", "enumNames": ["value.true", "value.false"] }, "wantAssertionsSigned": { "title": "label.want-assertions-signed", "description": "tooltip.want-assertions-signed", "type": "boolean", "enumNames": ["value.true", "value.false"] }, "x509Certificates": { "title": "label.x509-certificates", "type": "array", "items": { "$ref": "#/definitions/Certificate" } } } }, "assertionConsumerServices": { "title": "label.assertion-consumer-service-endpoints", "description": "", "type": "array", "items": { "$ref": "#/definitions/AssertionConsumerService" } }, "serviceProviderSsoDescriptor": { "type": "object", "properties": { "protocolSupportEnum": { "title": "label.protocol-support-enumeration", "description": "tooltip.protocol-support-enumeration", "type": "string", "widget": { "id": "select" }, "oneOf": [{ "enum": ["SAML 2"], "description": "SAML 2" }, { "enum": ["SAML 1.1"], "description": "SAML 1.1" }] }, "nameIdFormats": { "$ref": "#/definitions/nameIdFormats" } }, "dependencies": { "nameIdFormats": ["protocolSupportEnum"] } }, "logoutEndpoints": { "title": "label.logout-endpoints", "description": "tooltip.logout-endpoints", "type": "array", "items": { "$ref": "#/definitions/LogoutEndpoint" } }, "relyingPartyOverrides": { "type": "object", "properties": { "signAssertion": { "title": "label.sign-the-assertion", "description": "tooltip.sign-assertion", "type": "boolean", "default": false }, "dontSignResponse": { "title": "label.dont-sign-the-response", "description": "tooltip.dont-sign-response", "type": "boolean", "default": false }, "turnOffEncryption": { "title": "label.turn-off-encryption-of-response", "description": "tooltip.turn-off-encryption", "type": "boolean", "default": false }, "useSha": { "title": "label.use-sha1-signing-algorithm", "description": "tooltip.usa-sha-algorithm", "type": "boolean", "default": false }, "ignoreAuthenticationMethod": { "title": "label.ignore-any-sp-requested-authentication-method", "description": "tooltip.ignore-auth-method", "type": "boolean", "default": false }, "omitNotBefore": { "title": "label.omit-not-before-condition", "description": "tooltip.omit-not-before-condition", "type": "boolean", "default": false }, "responderId": { "title": "label.responder-id", "description": "tooltip.responder-id", "type": "string", "default": "" }, "nameIdFormats": { "$ref": "#/definitions/nameIdFormats" }, "authenticationMethods": { "$ref": "#/definitions/authenticationMethods" }, "forceAuthn": { "title": "label.force-authn", "description": "tooltip.force-authn", "type": "boolean", "default": false } } }, "attributeRelease": { "type": "array", "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "items": { "type": "string", "enum": ["eduPersonPrincipalName", "uid", "mail", "surname", "givenName", "eduPersonAffiliation", "eduPersonScopedAffiliation", "eduPersonPrimaryAffiliation", "eduPersonEntitlement", "eduPersonAssurance", "eduPersonUniqueId", "employeeNumber"] }, "uniqueItems": true } }, "definitions": { "Contact": { "type": "object", "required": ["name", "type", "emailAddress"], "properties": { "name": { "title": "label.contact-name", "description": "tooltip.contact-name", "type": "string", "minLength": 1, "maxLength": 255 }, "type": { "title": "label.contact-type", "description": "tooltip.contact-type", "type": "string", "widget": "select", "minLength": 1, "oneOf": [{ "enum": ["support"], "description": "value.support" }, { "enum": ["technical"], "description": "value.technical" }, { "enum": ["administrative"], "description": "value.administrative" }, { "enum": ["other"], "description": "value.other" }] }, "emailAddress": { "title": "label.contact-email-address", "description": "tooltip.contact-email", "type": "string", "pattern": "^(mailto:)?(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$", "minLength": 1, "maxLength": 255 } } }, "Certificate": { "type": "object", "required": ["type", "value"], "properties": { "name": { "title": "label.certificate-name-display-only", "description": "tooltip.certificate-name", "type": "string", "maxLength": 255 }, "type": { "title": "label.certificate-type", "type": "string", "widget": { "id": "radio", "class": "form-check-inline" }, "oneOf": [{ "enum": ["signing"], "description": "value.signing" }, { "enum": ["encryption"], "description": "value.encryption" }, { "enum": ["both"], "description": "value.both" }] }, "value": { "title": "label.certificate", "description": "tooltip.certificate", "type": "string", "widget": "textarea", "minLength": 1 } } }, "AssertionConsumerService": { "type": "object", "required": ["locationUrl", "binding"], "properties": { "locationUrl": { "title": "label.assertion-consumer-service-location", "description": "tooltip.assertion-consumer-service-location", "type": "string", "widget": { "id": "string", "help": "message.valid-url" }, "minLength": 1, "maxLength": 255 }, "binding": { "title": "label.assertion-consumer-service-location-binding", "description": "tooltip.assertion-consumer-service-location-binding", "type": "string", "widget": "select", "oneOf": [{ "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:PAOS"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:PAOS" }, { "enum": ["urn:oasis:names:tc:SAML:1.0:profiles:browser-post"], "description": "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" }, { "enum": ["urn:oasis:names:tc:SAML:1.0:profiles:artifact-01"], "description": "urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" }] }, "makeDefault": { "title": "label.mark-as-default", "description": "tooltip.mark-as-default", "type": "boolean" } } }, "LogoutEndpoint": { "description": "tooltip.new-endpoint", "type": "object", "fieldsets": [{ "fields": ["url", "bindingType"] }], "required": ["url", "bindingType"], "properties": { "url": { "title": "label.url", "description": "tooltip.url", "type": "string", "minLength": 1, "maxLength": 255 }, "bindingType": { "title": "label.binding-type", "description": "tooltip.binding-type", "type": "string", "widget": "select", "oneOf": [{ "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:SOAP"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" }, { "enum": ["urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"], "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" }] } } }, "MDUI": { "type": "object", "widget": { "id": "fieldset" }, "fieldsets": [{ "type": "group", "fields": ["displayName", "informationUrl", "description"] }, { "type": "group", "fields": ["privacyStatementUrl", "logoUrl", "logoWidth", "logoHeight"] }], "properties": { "displayName": { "title": "label.display-name", "description": "tooltip.mdui-display-name", "type": "string", "minLength": 1, "maxLength": 255 }, "informationUrl": { "title": "label.information-url", "description": "tooltip.mdui-information-url", "type": "string", "minLength": 1, "maxLength": 255 }, "privacyStatementUrl": { "title": "label.privacy-statement-url", "description": "tooltip.mdui-privacy-statement-url", "type": "string", "minLength": 1, "maxLength": 255 }, "description": { "title": "label.description", "description": "tooltip.mdui-description", "type": "string", "widget": { "id": "textarea" }, "minLength": 1, "maxLength": 255 }, "logoUrl": { "title": "label.logo-url", "description": "tooltip.mdui-logo-url", "type": "string", "minLength": 1, "maxLength": 255 }, "logoHeight": { "title": "label.logo-height", "description": "tooltip.mdui-logo-height", "minimum": 0, "type": "integer" }, "logoWidth": { "title": "label.logo-width", "description": "tooltip.mdui-logo-width", "minimum": 0, "type": "integer" } } }, "Organization": { "type": "object", "properties": { "name": { "title": "label.organization-name", "description": "tooltip.organization-name", "type": "string", "minLength": 1, "maxLength": 255 }, "displayName": { "title": "label.organization-display-name", "description": "tooltip.organization-display-name", "type": "string", "minLength": 1, "maxLength": 255 }, "url": { "title": "label.organization-url", "description": "tooltip.organization-url", "type": "string", "minLength": 1, "maxLength": 255 } }, "dependencies": { "name": { "required": ["displayName", "url"] }, "displayName": { "required": ["name", "url"] }, "url": { "required": ["name", "displayName"] } } }, "nameIdFormats": { "title": "label.nameid-format-to-send", "description": "tooltip.nameid-format", "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1, "maxLength": 255, "examples": ["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"] } }, "authenticationMethods": { "title": "label.authentication-methods-to-use", "description": "tooltip.authentication-methods-to-use", "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1, "maxLength": 255, "examples": ["https://refeds.org/profile/mfa", "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"] } } } }; + +export default SCHEMA; \ No newline at end of file diff --git a/ui/src/testing/uiSchema.js b/ui/src/testing/uiSchema.js new file mode 100644 index 000000000..eb557bcf2 --- /dev/null +++ b/ui/src/testing/uiSchema.js @@ -0,0 +1,216 @@ +const schema = { + "ui:order": { + "0": "serviceProviderName", + "1": "*", + "ui:widget": "hidden" + }, + "layout": { + "groups": [ + { + "size": 6, + "fields": [ + "serviceProviderName", + "entityId", + "serviceEnabled", + "organization" + ] + }, + { + "size": 6, + "fields": [ + "contacts" + ] + }, + { + "size": 12, + "fields": [ + "mdui" + ] + }, + { + "size": 6, + "fields": [ + "serviceProviderSsoDescriptor" + ] + }, + { + "size": 6, + "fields": [ + "logoutEndpoints" + ] + }, + { + "size": 12, + "fields": [ + "securityInfo" + ] + }, + { + "size": 6, + "fields": [ + "assertionConsumerServices" + ] + }, + { + "size": 6, + "fields": [ + "relyingPartyOverrides" + ] + }, + { + "size": 6, + "fields": [ + "attributeRelease" + ] + } + ], + "ui:widget": "hidden" + }, + "contacts": { + "ui:options": { + "orderable": false + }, + "type": "contact", + "ui:title": false + }, + "attributeRelease": { + "ui:widget": "hidden" + }, + "logoutEndpoints": { + "type": "endpoint", + "ui:options": { + "orderable": false + }, + "ui:title": false, + "ui:widget": "hidden" + }, + "assertionConsumerServices": { + "type": "service", + "ui:options": { + "orderable": false + }, + "ui:title": false, + "ui:widget": "hidden" + }, + "relyingPartyOverrides": { + "nameIdFormats": { + "ui:options": { + "orderable": false + }, + "items": { + "ui:widget": "OptionWidget" + } + }, + "authenticationMethods": { + "ui:options": { + "orderable": false + }, + "items": { + "ui:widget": "OptionWidget" + } + }, + "ui:widget": "hidden" + }, + "serviceProviderSsoDescriptor": { + "protocolSupportEnum": { + "ui:placeholder": "label.select-protocol" + }, + "nameIdFormats": { + "ui:options": { + "orderable": false + }, + "items": { + "ui:widget": "OptionWidget" + } + }, + "ui:widget": "hidden" + }, + "securityInfo": { + "layout": { + "groups": [ + { + "size": 6, + "fields": [ + "authenticationRequestsSigned", + "wantAssertionsSigned", + "x509Certificates" + ] + } + ] + }, + "x509CertificateAvailable": { + "ui:widget": "hidden" + }, + "authenticationRequestsSigned": { + "ui:widget": "radio", + "ui:options": { + "inline": true + } + }, + "wantAssertionsSigned": { + "ui:widget": "radio", + "ui:options": { + "inline": true + } + }, + "x509Certificates": { + "type": "certificate", + "ui:options": { + "orderable": false + }, + "items": { + "type": { + "ui:widget": "radio", + "ui:description": false, + "ui:options": { + "inline": true + } + }, + "value": { + "ui:widget": "textarea" + } + } + }, + "ui:widget": "hidden" + }, + "mdui": { + "layout": { + "groups": [ + { + "size": 6, + "fields": [ + "displayName", + "informationUrl", + "description" + ] + }, + { + "size": 6, + "fields": [ + "privacyStatementUrl", + "logoUrl", + "logoWidth", + "logoHeight" + ] + } + ] + }, + "description": { + "ui:widget": "textarea" + }, + "logoHeight": { + "ui:widget": "updown" + }, + "logoWidth": { + "ui:widget": "updown" + }, + "ui:widget": "hidden" + }, + "serviceProviderName": {}, + "entityId": {}, + "serviceEnabled": {}, + "organization": {}, + "ui:disabled": false +}; + +export default schema; \ No newline at end of file diff --git a/ui/yarn.lock b/ui/yarn.lock index 94dfe0128..dcbc3d1c9 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1197,6 +1197,11 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz#01dd3d054da07a00b764d78748df20daf2b317e9" integrity sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw== +"@fortawesome/fontawesome-free@^5.15.3": + version "5.15.3" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a" + integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w== + "@fortawesome/fontawesome-svg-core@^1.2.35": version "1.2.35" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz#85aea8c25645fcec88d35f2eb1045c38d3e65cff" @@ -1702,10 +1707,10 @@ lz-string "^1.4.4" pretty-format "^26.6.2" -"@testing-library/jest-dom@^5.11.4": - version "5.12.0" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz#6a5d340b092c44b7bce17a4791b47d9bc2c61443" - integrity sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow== +"@testing-library/jest-dom@^5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.13.0.tgz#0a365684e2c1159f857f5915be50089fc5657df0" + integrity sha512-+jXXTn8GjRnZkJfzG/tqK/2Q7dGlBInR412WE7Aml7CT3wdSpx5dMQC0HOwVQoZ3cNTmQUy8fCVGUV/Zhoyvcw== dependencies: "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" @@ -1716,10 +1721,10 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^11.1.0": - version "11.2.6" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" - integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ== +"@testing-library/react@^11.2.7": + version "11.2.7" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" + integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1"