diff options
author | Florian Dold <florian@dold.me> | 2021-10-11 10:58:55 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2021-10-11 10:58:55 +0200 |
commit | f23a8ee4d356645ae3f91862552b256f230c6bcb (patch) | |
tree | 641bf808ef6c6bf7eb003d96f6f7ed5654f854b8 /packages | |
parent | 0bbaafcd36ce68f95faee0b91738a169848c7a90 (diff) |
anastasis-webui: first commit
Diffstat (limited to 'packages')
33 files changed, 738 insertions, 0 deletions
diff --git a/packages/anastasis-webui/.gitignore b/packages/anastasis-webui/.gitignore new file mode 100644 index 000000000..7e0633d5e --- /dev/null +++ b/packages/anastasis-webui/.gitignore @@ -0,0 +1,3 @@ +node_modules +/build +/*.log diff --git a/packages/anastasis-webui/README.md b/packages/anastasis-webui/README.md new file mode 100644 index 000000000..9f9b5987d --- /dev/null +++ b/packages/anastasis-webui/README.md @@ -0,0 +1,19 @@ +# anastasis-webui + +## CLI Commands +* `npm install`: Installs dependencies + +* `npm run dev`: Run a development, HMR server + +* `npm run serve`: Run a production-like server + +* `npm run build`: Production-ready build + +* `npm run lint`: Pass TypeScript files using ESLint + +* `npm run test`: Run Jest and Enzyme with + [`enzyme-adapter-preact-pure`](https://github.com/preactjs/enzyme-adapter-preact-pure) for + your tests + + +For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json new file mode 100644 index 000000000..ddbd9ef20 --- /dev/null +++ b/packages/anastasis-webui/package.json @@ -0,0 +1,50 @@ +{ + "private": true, + "name": "anastasis-webui", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "build": "preact build", + "serve": "sirv build --port 8080 --cors --single", + "dev": "preact watch", + "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", + "test": "jest ./tests" + }, + "eslintConfig": { + "parser": "@typescript-eslint/parser", + "extends": [ + "preact", + "plugin:@typescript-eslint/recommended" + ], + "ignorePatterns": [ + "build/" + ] + }, + "dependencies": { + "preact": "^10.3.1", + "preact-render-to-string": "^5.1.4", + "preact-router": "^3.2.1" + }, + "devDependencies": { + "@types/enzyme": "^3.10.5", + "@types/jest": "^26.0.8", + "@typescript-eslint/eslint-plugin": "^2.25.0", + "@typescript-eslint/parser": "^2.25.0", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^3.1.0", + "eslint": "^6.8.0", + "eslint-config-preact": "^1.1.1", + "jest": "^26.2.2", + "jest-preset-preact": "^4.0.2", + "preact-cli": "^3.0.0", + "sirv-cli": "^1.0.0-next.3", + "typescript": "^3.7.5" + }, + "jest": { + "preset": "jest-preset-preact", + "setupFiles": [ + "<rootDir>/tests/__mocks__/browserMocks.ts", + "<rootDir>/tests/__mocks__/setupTests.ts" + ] + } +}
\ No newline at end of file diff --git a/packages/anastasis-webui/src/.babelrc b/packages/anastasis-webui/src/.babelrc new file mode 100644 index 000000000..123002210 --- /dev/null +++ b/packages/anastasis-webui/src/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "preact-cli/babel" + ] +} diff --git a/packages/anastasis-webui/src/assets/favicon.ico b/packages/anastasis-webui/src/assets/favicon.ico Binary files differnew file mode 100644 index 000000000..07419145b --- /dev/null +++ b/packages/anastasis-webui/src/assets/favicon.ico diff --git a/packages/anastasis-webui/src/assets/icons/android-chrome-192x192.png b/packages/anastasis-webui/src/assets/icons/android-chrome-192x192.png Binary files differnew file mode 100644 index 000000000..93ebe2e2c --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/android-chrome-192x192.png diff --git a/packages/anastasis-webui/src/assets/icons/android-chrome-512x512.png b/packages/anastasis-webui/src/assets/icons/android-chrome-512x512.png Binary files differnew file mode 100644 index 000000000..52d1623ea --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/android-chrome-512x512.png diff --git a/packages/anastasis-webui/src/assets/icons/apple-touch-icon.png b/packages/anastasis-webui/src/assets/icons/apple-touch-icon.png Binary files differnew file mode 100644 index 000000000..254e4bb4d --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/apple-touch-icon.png diff --git a/packages/anastasis-webui/src/assets/icons/favicon-16x16.png b/packages/anastasis-webui/src/assets/icons/favicon-16x16.png Binary files differnew file mode 100644 index 000000000..e81177dcb --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/favicon-16x16.png diff --git a/packages/anastasis-webui/src/assets/icons/favicon-32x32.png b/packages/anastasis-webui/src/assets/icons/favicon-32x32.png Binary files differnew file mode 100644 index 000000000..40e9b5b47 --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/favicon-32x32.png diff --git a/packages/anastasis-webui/src/assets/icons/mstile-150x150.png b/packages/anastasis-webui/src/assets/icons/mstile-150x150.png Binary files differnew file mode 100644 index 000000000..9cfb889be --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/mstile-150x150.png diff --git a/packages/anastasis-webui/src/components/app.tsx b/packages/anastasis-webui/src/components/app.tsx new file mode 100644 index 000000000..5abb12a3d --- /dev/null +++ b/packages/anastasis-webui/src/components/app.tsx @@ -0,0 +1,23 @@ +import { FunctionalComponent, h } from 'preact'; +import { Route, Router } from 'preact-router'; + +import Home from '../routes/home'; +import Profile from '../routes/profile'; +import NotFoundPage from '../routes/notfound'; +import Header from './header'; + +const App: FunctionalComponent = () => { + return ( + <div id="preact_root"> + <Header /> + <Router> + <Route path="/" component={Home} /> + <Route path="/profile/" component={Profile} user="me" /> + <Route path="/profile/:user" component={Profile} /> + <NotFoundPage default /> + </Router> + </div> + ); +}; + +export default App; diff --git a/packages/anastasis-webui/src/components/header/index.tsx b/packages/anastasis-webui/src/components/header/index.tsx new file mode 100644 index 000000000..f2b6fe8ad --- /dev/null +++ b/packages/anastasis-webui/src/components/header/index.tsx @@ -0,0 +1,24 @@ +import { FunctionalComponent, h } from 'preact'; +import { Link } from 'preact-router/match'; +import style from './style.css'; + +const Header: FunctionalComponent = () => { + return ( + <header class={style.header}> + <h1>Preact App</h1> + <nav> + <Link activeClassName={style.active} href="/"> + Home + </Link> + <Link activeClassName={style.active} href="/profile"> + Me + </Link> + <Link activeClassName={style.active} href="/profile/john"> + John + </Link> + </nav> + </header> + ); +}; + +export default Header; diff --git a/packages/anastasis-webui/src/components/header/style.css b/packages/anastasis-webui/src/components/header/style.css new file mode 100644 index 000000000..f08fda702 --- /dev/null +++ b/packages/anastasis-webui/src/components/header/style.css @@ -0,0 +1,48 @@ +.header { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 56px; + padding: 0; + background: #673AB7; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); + z-index: 50; +} + +.header h1 { + float: left; + margin: 0; + padding: 0 15px; + font-size: 24px; + line-height: 56px; + font-weight: 400; + color: #FFF; +} + +.header nav { + float: right; + font-size: 100%; +} + +.header nav a { + display: inline-block; + height: 56px; + line-height: 56px; + padding: 0 15px; + min-width: 50px; + text-align: center; + background: rgba(255,255,255,0); + text-decoration: none; + color: #FFF; + will-change: background-color; +} + +.header nav a:hover, +.header nav a:active { + background: rgba(0,0,0,0.2); +} + +.header nav a.active { + background: rgba(0,0,0,0.4); +} diff --git a/packages/anastasis-webui/src/declaration.d.ts b/packages/anastasis-webui/src/declaration.d.ts new file mode 100644 index 000000000..bbff36e8a --- /dev/null +++ b/packages/anastasis-webui/src/declaration.d.ts @@ -0,0 +1,4 @@ +declare module "*.css" { + const mapping: Record<string, string>; + export default mapping; +} diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts new file mode 100644 index 000000000..30bab96d1 --- /dev/null +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -0,0 +1,131 @@ +import { useState } from "preact/hooks"; + +type ReducerState = any; + +interface AnastasisState { + reducerState: ReducerState | undefined; + currentError: any; +} + +export enum BackupStates { + ContinentSelecting = "CONTINENT_SELECTING", + CountrySelecting = "COUNTRY_SELECTING", +} + +export enum RecoveryStates { + ContinentSelecting = "CONTINENT_SELECTING", + CountrySelecting = "COUNTRY_SELECTING", +} + +const reducerBaseUrl = "http://localhost:5000/"; + +async function getBackupStartState(): Promise<ReducerState> { + const resp = await fetch(new URL("start-backup", reducerBaseUrl).href); + return await resp.json(); +} + +async function getRecoveryStartState(): Promise<ReducerState> { + const resp = await fetch(new URL("start-recovery", reducerBaseUrl).href); + return await resp.json(); +} + +async function reduceState( + state: any, + action: string, + args: any, +): Promise<ReducerState> { + const resp = await fetch(new URL("action", reducerBaseUrl).href, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + state, + action, + arguments: args, + }), + }); + return resp.json(); +} + +export interface AnastasisReducerApi { + currentReducerState: ReducerState; + currentError: any; + startBackup: () => void; + startRecover: () => void; + back: () => void; + transition(action: string, args: any): void; +} + +export function useAnastasisReducer(): AnastasisReducerApi { + const [anastasisState, setAnastasisState] = useState<AnastasisState>({ + reducerState: undefined, + currentError: undefined, + }); + + async function doTransition(action: string, args: any) { + console.log("reducing with", action, args); + const s = await reduceState(anastasisState.reducerState, action, args); + console.log("got new state from reducer", s); + if (s.code) { + setAnastasisState({ ...anastasisState, currentError: s }); + } else { + setAnastasisState({ + ...anastasisState, + currentError: undefined, + reducerState: s, + }); + } + } + + return { + currentReducerState: anastasisState.reducerState, + currentError: anastasisState.currentError, + async startBackup() { + const s = await getBackupStartState(); + setAnastasisState({ + ...anastasisState, + currentError: undefined, + reducerState: s, + }); + }, + async startRecover() { + const s = await getRecoveryStartState(); + setAnastasisState({ + ...anastasisState, + currentError: undefined, + reducerState: s, + }); + }, + transition(action: string, args: any) { + doTransition(action, args); + }, + back() { + if ( + anastasisState.reducerState.backup_state === + BackupStates.ContinentSelecting || + anastasisState.reducerState.recovery_state === + RecoveryStates.ContinentSelecting + ) { + setAnastasisState({ + ...anastasisState, + currentError: undefined, + reducerState: undefined, + }); + } else if ( + anastasisState.reducerState.backup_state === + BackupStates.CountrySelecting + ) { + doTransition("unselect_continent", {}); + } else if ( + anastasisState.reducerState.recovery_state === + RecoveryStates.CountrySelecting + ) { + doTransition("unselect_continent", {}); + } else { + doTransition("back", {}); + } + }, + }; +} diff --git a/packages/anastasis-webui/src/index.ts b/packages/anastasis-webui/src/index.ts new file mode 100644 index 000000000..3b3f78442 --- /dev/null +++ b/packages/anastasis-webui/src/index.ts @@ -0,0 +1,4 @@ +import './style/index.css'; +import App from './components/app'; + +export default App; diff --git a/packages/anastasis-webui/src/manifest.json b/packages/anastasis-webui/src/manifest.json new file mode 100644 index 000000000..6b44a2b31 --- /dev/null +++ b/packages/anastasis-webui/src/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "anastasis-webui", + "short_name": "anastasis-webui", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "background_color": "#fff", + "theme_color": "#673ab8", + "icons": [ + { + "src": "/assets/icons/android-chrome-192x192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/assets/icons/android-chrome-512x512.png", + "type": "image/png", + "sizes": "512x512" + } + ] +}
\ No newline at end of file diff --git a/packages/anastasis-webui/src/routes/home/index.tsx b/packages/anastasis-webui/src/routes/home/index.tsx new file mode 100644 index 000000000..ee3399503 --- /dev/null +++ b/packages/anastasis-webui/src/routes/home/index.tsx @@ -0,0 +1,187 @@ +import { FunctionalComponent, h } from "preact"; +import { useState } from "preact/hooks"; +import { + AnastasisReducerApi, + useAnastasisReducer, +} from "../../hooks/use-anastasis-reducer"; +import style from "./style.css"; + +const Home: FunctionalComponent = () => { + const reducer = useAnastasisReducer(); + if (!reducer.currentReducerState) { + return ( + <div class={style.home}> + <h1>Home</h1> + <p> + <button onClick={() => reducer.startBackup()}>Backup</button> + <button>Recover</button> + </p> + </div> + ); + } + console.log("state", reducer.currentReducerState); + if (reducer.currentReducerState.backup_state === "CONTINENT_SELECTING") { + return ( + <div class={style.home}> + <h1>Backup: Select Continent</h1> + <ErrorBanner reducer={reducer} /> + <div> + {reducer.currentReducerState.continents.map((x: any) => { + const sel = (x: string) => + reducer.transition("select_continent", { continent: x }); + return ( + <button onClick={() => sel(x.name)} key={x.name}> + {x.name} + </button> + ); + })} + </div> + <div> + <button onClick={() => reducer.back()}>Back</button> + </div> + </div> + ); + } + if (reducer.currentReducerState.backup_state === "COUNTRY_SELECTING") { + return ( + <div class={style.home}> + <h1>Backup: Select Continent</h1> + <ErrorBanner reducer={reducer} /> + <div> + {reducer.currentReducerState.countries.map((x: any) => { + const sel = (x: any) => + reducer.transition("select_country", { + country_code: x.code, + currencies: [x.currency], + }); + return ( + <button onClick={() => sel(x)} key={x.name}> + {x.name} ({x.currency}) + </button> + ); + })} + </div> + <div> + <button onClick={() => reducer.back()}>Back</button> + </div> + </div> + ); + } + if ( + reducer.currentReducerState.backup_state === "USER_ATTRIBUTES_COLLECTING" + ) { + return <AttributeEntry reducer={reducer} />; + } + + if (reducer.currentReducerState.backup_state === "AUTHENTICATIONS_EDITING") { + return <AuthenticationEditor reducer={reducer} />; + } + + console.log("unknown state", reducer.currentReducerState); + return ( + <div class={style.home}> + <h1>Home</h1> + <p>Bug: Unknown state.</p> + </div> + ); +}; + +export interface AuthenticationEditorProps { + reducer: AnastasisReducerApi; +} + +function AuthenticationEditor(props: AuthenticationEditorProps) { + const { reducer } = props; + const providers = reducer.currentReducerState.authentication_providers; + const authAvailable = new Set<string>(); + for (const provKey of Object.keys(providers)) { + const p = providers[provKey]; + for (const meth of p.methods) { + authAvailable.add(meth.type); + } + } + return ( + <div class={style.home}> + <h1>Backup: Configure Authentication Methods</h1> + <p>Auths available: {JSON.stringify(Array.from(authAvailable))}</p> + <button>Next</button> + <div> + <button onClick={() => reducer.back()}>Back</button> + </div> + </div> + ); +} + +export interface AttributeEntryProps { + reducer: AnastasisReducerApi; +} + +function AttributeEntry(props: AttributeEntryProps) { + const reducer = props.reducer; + const [attrs, setAttrs] = useState<Record<string, string>>({}); + return ( + <div class={style.home}> + <h1>Backup: Enter Basic User Attributes</h1> + <ErrorBanner reducer={reducer} /> + <div> + {reducer.currentReducerState.required_attributes.map((x: any) => { + return ( + <AttributeEntryField + setValue={(v: string) => setAttrs({ ...attrs, [x.name]: v })} + spec={x} + value={attrs[x.name]} + /> + ); + })} + </div> + <button + onClick={() => + reducer.transition("enter_user_attributes", { + identity_attributes: attrs, + }) + } + > + Next + </button> + <div> + <button onClick={() => reducer.back()}>Back</button> + </div> + </div> + ); +} + +export interface AttributeEntryFieldProps { + value: string; + setValue: (newValue: string) => void; + spec: any; +} + +function AttributeEntryField(props: AttributeEntryFieldProps) { + return ( + <div> + <label>{props.spec.label}</label> + <input + type="text" + value={props.value} + onChange={(e) => props.setValue((e as any).target.value)} + /> + </div> + ); +} + +interface ErrorBannerProps { + reducer: AnastasisReducerApi; +} + +/** + * Show a dismissable error banner if there is a current error. + */ +function ErrorBanner(props: ErrorBannerProps) { + const currentError = props.reducer.currentError; + if (currentError) { + return <div>Error: {JSON.stringify(currentError)}</div>; + } + return null; +} + +export default Home; diff --git a/packages/anastasis-webui/src/routes/home/style.css b/packages/anastasis-webui/src/routes/home/style.css new file mode 100644 index 000000000..f052d2546 --- /dev/null +++ b/packages/anastasis-webui/src/routes/home/style.css @@ -0,0 +1,5 @@ +.home { + padding: 56px 20px; + min-height: 100%; + width: 100%; +} diff --git a/packages/anastasis-webui/src/routes/notfound/index.tsx b/packages/anastasis-webui/src/routes/notfound/index.tsx new file mode 100644 index 000000000..444e03d44 --- /dev/null +++ b/packages/anastasis-webui/src/routes/notfound/index.tsx @@ -0,0 +1,17 @@ +import { FunctionalComponent, h } from 'preact'; +import { Link } from 'preact-router/match'; +import style from './style.css'; + +const Notfound: FunctionalComponent = () => { + return ( + <div class={style.notfound}> + <h1>Error 404</h1> + <p>That page doesn't exist.</p> + <Link href="/"> + <h4>Back to Home</h4> + </Link> + </div> + ); +}; + +export default Notfound; diff --git a/packages/anastasis-webui/src/routes/notfound/style.css b/packages/anastasis-webui/src/routes/notfound/style.css new file mode 100644 index 000000000..3b4932804 --- /dev/null +++ b/packages/anastasis-webui/src/routes/notfound/style.css @@ -0,0 +1,4 @@ +.notfound { + padding: 0 5%; + margin: 100px 0; +}
\ No newline at end of file diff --git a/packages/anastasis-webui/src/routes/profile/index.tsx b/packages/anastasis-webui/src/routes/profile/index.tsx new file mode 100644 index 000000000..023b56c9d --- /dev/null +++ b/packages/anastasis-webui/src/routes/profile/index.tsx @@ -0,0 +1,44 @@ +import { FunctionalComponent, h } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; +import style from './style.css'; + +interface Props { + user: string; +} + +const Profile: FunctionalComponent<Props> = (props: Props) => { + const { user } = props; + const [time, setTime] = useState<number>(Date.now()); + const [count, setCount] = useState<number>(0); + + // gets called when this route is navigated to + useEffect(() => { + const timer = window.setInterval(() => setTime(Date.now()), 1000); + + // gets called just before navigating away from the route + return (): void => { + clearInterval(timer); + }; + }, []); + + // update the current time + const increment = (): void => { + setCount(count + 1); + }; + + return ( + <div class={style.profile}> + <h1>Profile: {user}</h1> + <p>This is the user profile for a user named {user}.</p> + + <div>Current time: {new Date(time).toLocaleString()}</div> + + <p> + <button onClick={increment}>Click Me</button> Clicked {count}{' '} + times. + </p> + </div> + ); +}; + +export default Profile; diff --git a/packages/anastasis-webui/src/routes/profile/style.css b/packages/anastasis-webui/src/routes/profile/style.css new file mode 100644 index 000000000..fcb129627 --- /dev/null +++ b/packages/anastasis-webui/src/routes/profile/style.css @@ -0,0 +1,5 @@ +.profile { + padding: 56px 20px; + min-height: 100%; + width: 100%; +} diff --git a/packages/anastasis-webui/src/style/index.css b/packages/anastasis-webui/src/style/index.css new file mode 100644 index 000000000..5fe95de60 --- /dev/null +++ b/packages/anastasis-webui/src/style/index.css @@ -0,0 +1,20 @@ +html, body { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + background: #FAFAFA; + font-family: 'Helvetica Neue', arial, sans-serif; + font-weight: 400; + color: #444; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +#app { + height: 100%; +} diff --git a/packages/anastasis-webui/src/sw.js b/packages/anastasis-webui/src/sw.js new file mode 100644 index 000000000..146aa01dd --- /dev/null +++ b/packages/anastasis-webui/src/sw.js @@ -0,0 +1,4 @@ +import { getFiles, setupPrecaching, setupRouting } from 'preact-cli/sw/'; + +setupRouting(); +setupPrecaching(getFiles()); diff --git a/packages/anastasis-webui/src/template.html b/packages/anastasis-webui/src/template.html new file mode 100644 index 000000000..770c48b2b --- /dev/null +++ b/packages/anastasis-webui/src/template.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title><% preact.title %></title> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta name="mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-capable" content="yes"> + <link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png"> + <% preact.headEnd %> + </head> + <body> + <% preact.bodyEnd %> + </body> +</html> diff --git a/packages/anastasis-webui/tests/__mocks__/browserMocks.ts b/packages/anastasis-webui/tests/__mocks__/browserMocks.ts new file mode 100644 index 000000000..5be8c3ce6 --- /dev/null +++ b/packages/anastasis-webui/tests/__mocks__/browserMocks.ts @@ -0,0 +1,21 @@ +// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage +/** + * An example how to mock localStorage is given below 👇 + */ + +/* +// Mocks localStorage +const localStorageMock = (function() { + let store = {}; + + return { + getItem: (key) => store[key] || null, + setItem: (key, value) => store[key] = value.toString(), + clear: () => store = {} + }; + +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock +}); */ diff --git a/packages/anastasis-webui/tests/__mocks__/fileMocks.ts b/packages/anastasis-webui/tests/__mocks__/fileMocks.ts new file mode 100644 index 000000000..87109e355 --- /dev/null +++ b/packages/anastasis-webui/tests/__mocks__/fileMocks.ts @@ -0,0 +1,3 @@ +// This fixed an error related to the CSS and loading gif breaking my Jest test +// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets +export default 'test-file-stub'; diff --git a/packages/anastasis-webui/tests/__mocks__/setupTests.ts b/packages/anastasis-webui/tests/__mocks__/setupTests.ts new file mode 100644 index 000000000..01dc92a29 --- /dev/null +++ b/packages/anastasis-webui/tests/__mocks__/setupTests.ts @@ -0,0 +1,6 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-preact-pure'; + +configure({ + adapter: new Adapter() +}); diff --git a/packages/anastasis-webui/tests/declarations.d.ts b/packages/anastasis-webui/tests/declarations.d.ts new file mode 100644 index 000000000..67e940277 --- /dev/null +++ b/packages/anastasis-webui/tests/declarations.d.ts @@ -0,0 +1,3 @@ +// Enable enzyme adapter's integration with TypeScript +// See: https://github.com/preactjs/enzyme-adapter-preact-pure#usage-with-typescript +/// <reference types="enzyme-adapter-preact-pure" /> diff --git a/packages/anastasis-webui/tests/header.test.tsx b/packages/anastasis-webui/tests/header.test.tsx new file mode 100644 index 000000000..b2cfc2f4d --- /dev/null +++ b/packages/anastasis-webui/tests/header.test.tsx @@ -0,0 +1,12 @@ +import { h } from 'preact'; +import Header from '../src/components/header'; +// See: https://github.com/preactjs/enzyme-adapter-preact-pure +import { shallow } from 'enzyme'; + +describe('Initial Test of the Header', () => { + test('Header renders 3 nav items', () => { + const context = shallow(<Header />); + expect(context.find('h1').text()).toBe('Preact App'); + expect(context.find('Link').length).toBe(3); + }); +}); diff --git a/packages/anastasis-webui/tsconfig.json b/packages/anastasis-webui/tsconfig.json new file mode 100644 index 000000000..14d4d0470 --- /dev/null +++ b/packages/anastasis-webui/tsconfig.json @@ -0,0 +1,60 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "ES5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ + "module": "ESNext", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation: */ + "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "jsxFactory": "h", /* Specify the JSX factory function to use when targeting react JSX emit, e.g. React.createElement or h. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "removeComments": true, /* Do not emit comments to output. */ + "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "esModuleInterop": true, /* */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Source Map Options */ + // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */ + }, + "include": ["src/**/*", "tests/**/*"] +} |