Skip to content

Commit

Permalink
feat(whitelabel): redirect to custom logout url when user logout
Browse files Browse the repository at this point in the history
test: add test for logout and improve other tests
refactor: improve load functions
test: refactor test and mock reporting and workers for all tests
refactor: use direct import for reporting and remove index file

refs: SHELL-121 (#280) SHELL-101 (#281)
  • Loading branch information
beawar authored Jul 11, 2023
1 parent dc42f36 commit f9d30b6
Show file tree
Hide file tree
Showing 34 changed files with 546 additions and 220 deletions.
3 changes: 2 additions & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default {
'!src/**/types/*', // exclude types
'!src/**/*.d.ts', // exclude declarations
'!src/test/*', // exclude test folder
'!**/__mocks__/**/*', // exclude manual mocks
'!src/workers/*' // FIXME: exclude worker folder which throws error because of the esm syntax
],

Expand Down Expand Up @@ -106,7 +107,7 @@ export default {
},

// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
modulePathIgnorePatterns: ['<rootDir>/.*/__mocks__'],

// Activates notifications for test results
// notify: false,
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
},
"scripts": {
"build:clean": "rm -rf lib && rm -rf dist && rm -rf pkg",
"test": "jest",
"test:ci": "jest --testTimeout=10000 --maxWorkers=50%",
"test:dev": "jest",
"test": "is-ci && npm run test:ci || npm run test:dev",
"prepare": "is-ci || husky install",
"prepack": "npm run build:clean && npm run build -- -d",
"postpublish": "rm -rf lib",
Expand Down
29 changes: 10 additions & 19 deletions src/boot/app/load-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,14 @@ import { AppLink } from '../../ui-extras/app-link';
import * as CONSTANTS from '../../constants';
import type { CarbonioModule } from '../../../types';
import { getAppSetters } from './app-loader-setters';
import { report } from '../../reporting';
import { report } from '../../reporting/functions';
import SettingsHeader from '../../settings/components/settings-header';

export const _scripts: { [pkgName: string]: HTMLScriptElement } = {};
let _scriptId = 0;

export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
return new Promise((_resolve, _reject) => {
let resolved = false;
const resolve = (): void => {
if (!resolved) {
resolved = true;
_resolve(appPkg);
}
};
const reject = (e: unknown): void => {
if (!resolved) {
resolved = true;
_reject(e);
}
};
return new Promise((resolve, reject) => {
try {
if (
window.__ZAPP_SHARED_LIBRARIES__ &&
Expand Down Expand Up @@ -61,7 +48,7 @@ export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
`%c loaded ${appPkg.name}`,
'color: white; background: #539507;padding: 4px 8px 2px 4px; font-family: sans-serif; border-radius: 12px; width: 100%'
);
resolve();
resolve(appPkg);
};

const script = document.createElement('script');
Expand All @@ -71,7 +58,8 @@ export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
script.setAttribute('data-is_app', 'true');
script.setAttribute('src', `${appPkg.js_entrypoint}`);
document.body.appendChild(script);
_scripts[`${appPkg.name}-loader-${(_scriptId += 1)}`] = script;
_scriptId += 1;
_scripts[`${appPkg.name}-loader-${_scriptId}`] = script;
} catch (err: unknown) {
console.error(err);
reject(err);
Expand All @@ -80,9 +68,12 @@ export function loadApp(appPkg: CarbonioModule): Promise<CarbonioModule> {
}

export function unloadApps(): Promise<void> {
return Promise.resolve().then(() => {
return new Promise((resolve) => {
forOwn(_scripts, (script) => {
if (script.parentNode) script.parentNode.removeChild(script);
if (script.parentNode) {
script.parentNode.removeChild(script);
}
});
resolve();
});
}
11 changes: 6 additions & 5 deletions src/boot/app/load-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { registerLocale, setDefaultLocale } from '@zextras/carbonio-design-syste
import type { Locale as DateFnsLocale } from 'date-fns';
import { CarbonioModule } from '../../../types';
import { SHELL_APP_ID } from '../../constants';
import { useReporter } from '../../reporting';
import { useReporter } from '../../reporting/store';
import { getUserSetting, useAccountStore } from '../../store/account';
import { getT, useI18nStore } from '../../store/i18n';
import { loadApp, unloadApps } from './load-app';
Expand All @@ -20,12 +20,13 @@ import { localeList } from '../../settings/components/utils';
const getDateFnsLocale = (locale: string): Promise<DateFnsLocale> =>
import(`date-fns/locale/${locale}/index.js`);

export function loadApps(apps: Array<CarbonioModule>): void {
export function loadApps(
apps: Array<CarbonioModule>
): Promise<PromiseSettledResult<CarbonioModule>[]> {
injectSharedLibraries();
const appsToLoad = filter(apps, (app) => {
if (app.name === SHELL_APP_ID) return false;
if (app.attrKey && getUserSetting('attrs', app.attrKey) === 'FALSE') return false;
return true;
return !(app.attrKey && getUserSetting('attrs', app.attrKey) === 'FALSE');
});
console.log(
'%cLOADING APPS',
Expand All @@ -46,7 +47,7 @@ export function loadApps(apps: Array<CarbonioModule>): void {
});
}
useReporter.getState().setClients(appsToLoad);
Promise.allSettled(map(appsToLoad, (app) => loadApp(app)));
return Promise.allSettled(map(appsToLoad, (app) => loadApp(app)));
}

export function unloadAllApps(): Promise<void> {
Expand Down
114 changes: 55 additions & 59 deletions src/boot/loader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ResponseResolver, rest, RestContext, RestRequest } from 'msw';
import { act, screen, waitFor } from '@testing-library/react';
import React from 'react';
import server from '../mocks/server';
import { getComponentsJson, GetComponentsJsonResponseBody } from '../mocks/handlers/components';
import { setup } from '../test/utils';

import { act, screen } from '@testing-library/react';
import { rest } from 'msw';

import { Loader } from './loader';
import { LOGIN_V3_CONFIG_PATH } from '../constants';
import { LoginConfigStore } from '../../types/loginConfig';
import { getLoginConfig } from '../mocks/handlers/login-config';
import { getInfoRequest } from '../mocks/handlers/getInfoRequest';

jest.mock('../workers');
jest.mock('../reporting/functions');
import { GetComponentsJsonResponseBody } from '../mocks/handlers/components';
import server, { waitForResponse } from '../mocks/server';
import { controlConsoleError, setup } from '../test/utils';

describe('Loader', () => {
test('If only getComponents request fails, the LoaderFailureModal appears', async () => {
Expand All @@ -29,8 +25,19 @@ describe('Loader', () => {
)
);

setup(<Loader />);
const loginRes = waitForResponse('get', LOGIN_V3_CONFIG_PATH);
const componentsRes = waitForResponse('get', '/static/iris/components.json');
const getInfoRes = waitForResponse('post', '/service/soap/GetInfoRequest');

setup(
<span data-testid={'loader'}>
<Loader />
</span>
);
await loginRes;
await screen.findByTestId('loader');
await componentsRes;
await getInfoRes;
const title = await screen.findByText('Something went wrong...');
act(() => {
jest.runOnlyPendingTimers();
Expand All @@ -40,24 +47,25 @@ describe('Loader', () => {

test('If only getInfo request fails, the LoaderFailureModal appears', async () => {
// TODO remove when SHELL-117 will be implemented
const actualConsoleError = console.error;
console.error = jest.fn<ReturnType<typeof console.error>, Parameters<typeof console.error>>(
(error, ...restParameter) => {
if (error === 'Unexpected end of JSON input') {
console.log('Controlled error', error, ...restParameter);
} else {
actualConsoleError(error, ...restParameter);
}
}
);
controlConsoleError('Unexpected end of JSON input');
// using getComponents and loginConfig default handlers
server.use(
rest.post('/service/soap/GetInfoRequest', (req, res, ctx) =>
res(ctx.status(503, 'Controlled error: fail getInfo request'))
)
);

setup(<Loader />);
const loginRes = waitForResponse('get', LOGIN_V3_CONFIG_PATH);
const componentsRes = waitForResponse('get', '/static/iris/components.json');
const getInfoRes = waitForResponse('post', '/service/soap/GetInfoRequest');
setup(
<span data-testid={'loader'}>
<Loader />
</span>
);
await loginRes;
await screen.findByTestId('loader');
await componentsRes;
await getInfoRes;

const title = await screen.findByText('Something went wrong...');
act(() => {
Expand All @@ -67,47 +75,35 @@ describe('Loader', () => {
});

test('If only loginConfig request fails, the LoaderFailureModal does not appear', async () => {
const getComponentsJsonHandler = jest.fn(getComponentsJson);
const getInfoHandler = jest.fn(getInfoRequest);
type LoginConfigHandler = ResponseResolver<
RestRequest<never, never>,
RestContext,
Partial<Omit<LoginConfigStore, 'loaded'>>
>;
const loginConfigHandler = jest.fn<
ReturnType<LoginConfigHandler>,
Parameters<LoginConfigHandler>
>((req, res, ctx) => res(ctx.status(503)));
server.use(
rest.get('/static/iris/components.json', getComponentsJsonHandler),
rest.post('/service/soap/GetInfoRequest', getInfoHandler),
rest.get(LOGIN_V3_CONFIG_PATH, loginConfigHandler)
server.use(rest.get(LOGIN_V3_CONFIG_PATH, (req, res, ctx) => res(ctx.status(503))));
const loginRes = waitForResponse('get', LOGIN_V3_CONFIG_PATH);
const componentsRes = waitForResponse('get', '/static/iris/components.json');
const getInfoRes = waitForResponse('post', '/service/soap/GetInfoRequest');
setup(
<span data-testid={'loader'}>
<Loader />
</span>
);

setup(<Loader />);

await waitFor(() => expect(loginConfigHandler).toHaveBeenCalled());
await waitFor(() => expect(getComponentsJsonHandler).toHaveBeenCalled());
await waitFor(() => expect(getInfoHandler).toHaveBeenCalled());

await loginRes;
await screen.findByTestId('loader');
await componentsRes;
await getInfoRes;
expect(screen.queryByText('Something went wrong...')).not.toBeInTheDocument();
});

test('If Loader requests do not fail, the LoaderFailureModal does not appear', async () => {
const loginConfigHandler = jest.fn(getLoginConfig);
const getComponentsJsonHandler = jest.fn(getComponentsJson);
const getInfoHandler = jest.fn(getInfoRequest);

server.use(
rest.get('/static/iris/components.json', getComponentsJsonHandler),
rest.post('/service/soap/GetInfoRequest', getInfoHandler),
rest.get(LOGIN_V3_CONFIG_PATH, loginConfigHandler)
const loginRes = waitForResponse('get', LOGIN_V3_CONFIG_PATH);
const componentsRes = waitForResponse('get', '/static/iris/components.json');
const getInfoRes = waitForResponse('post', '/service/soap/GetInfoRequest');
setup(
<span data-testid={'loader'}>
<Loader />
</span>
);
setup(<Loader />);

await waitFor(() => expect(loginConfigHandler).toHaveBeenCalled());
await waitFor(() => expect(getComponentsJsonHandler).toHaveBeenCalled());
await waitFor(() => expect(getInfoHandler).toHaveBeenCalled());
await loginRes;
await screen.findByTestId('loader');
await componentsRes;
await getInfoRes;

expect(screen.queryByText('Something went wrong...')).not.toBeInTheDocument();
});
Expand Down
2 changes: 1 addition & 1 deletion src/boot/loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useAppStore } from '../store/app';
import { getInfo } from '../network/get-info';
import { loadApps, unloadAllApps } from './app/load-apps';
import { loginConfig } from '../network/login-config';
import { goToLogin } from '../network/go-to-login';
import { goToLogin } from '../network/utils';
import { getComponents } from '../network/get-components';

export function isPromiseRejectedResult<T>(
Expand Down
21 changes: 9 additions & 12 deletions src/jest-env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { act, configure } from '@testing-library/react';
import dotenv from 'dotenv';
import failOnConsole from 'jest-fail-on-console';
import { noop } from 'lodash';

import server from './mocks/server';

dotenv.config();
Expand Down Expand Up @@ -47,17 +48,6 @@ beforeEach(() => {
})
});

Object.defineProperty(window, 'ResizeObserver', {
writable: true,
value: function ResizeObserverMock(): ResizeObserver {
return {
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
};
}
});

// cleanup local storage
window.localStorage.clear();

Expand All @@ -77,9 +67,16 @@ afterAll(() => {
});

afterEach(() => {
jest.runOnlyPendingTimers();
act(() => {
jest.runOnlyPendingTimers();
});
server.events.removeAllListeners();
server.resetHandlers();
act(() => {
window.resizeTo(1024, 768);
});
});

jest.mock<typeof import('./workers')>('./workers');
jest.mock<typeof import('./reporting/functions')>('./reporting/functions');
jest.mock<typeof import('./reporting/store')>('./reporting/store');
11 changes: 11 additions & 0 deletions src/jest-polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,14 @@ window.resizeTo = function resizeTo(width, height): void {
outerHeight: height
}).dispatchEvent(new this.Event('resize'));
};

Object.defineProperty(window, 'ResizeObserver', {
writable: true,
value: function ResizeObserverMock(): ResizeObserver {
return {
observe: noop,
unobserve: noop,
disconnect: noop
};
}
});
9 changes: 7 additions & 2 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
*/

import { type RequestHandler, rest } from 'msw';

import { getComponentsJson } from './handlers/components';
import { endSessionRequest } from './handlers/endSessionRequest';
import { getInfoRequest } from './handlers/getInfoRequest';
import { LOGIN_V3_CONFIG_PATH } from '../constants';
import { getLoginConfig } from './handlers/login-config';
import { getRightsRequest } from './handlers/getRightsRequest';
import { getLoginConfig } from './handlers/login-config';
import { rootHandler } from './handlers/rootHandler';
import { LOGIN_V3_CONFIG_PATH } from '../constants';

const handlers: RequestHandler[] = [
rest.get('/static/iris/components.json', getComponentsJson),
rest.post('/service/soap/GetInfoRequest', getInfoRequest),
rest.post('/service/soap/GetRightsRequest', getRightsRequest),
rest.post('/service/soap/EndSessionRequest', endSessionRequest),
rest.get(LOGIN_V3_CONFIG_PATH, getLoginConfig),
rest.get('/', rootHandler),
rest.get('/i18n/en.json', (request, response, context) => response(context.json({})))
];

Expand Down
Loading

0 comments on commit f9d30b6

Please sign in to comment.