Skip to content

Commit

Permalink
STSMACOM-886 provide LinkedUser component (#1555)
Browse files Browse the repository at this point in the history
Link to a user-details record if permissions allow, or return a
plaintext name.

Mocks shamelessly copied from ui-users.

Refs STSMACOM-886
  • Loading branch information
zburke authored Jan 23, 2025
1 parent 21e7622 commit e7b7764
Show file tree
Hide file tree
Showing 27 changed files with 2,558 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"env": {
"jest": true
},
"extends": "@folio/eslint-config-stripes",
"parser": "@babel/eslint-parser",
"rules": {
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
if: github.ref_name == github.event.repository.default_branch || github.event_name != 'push' || github.ref_type == 'tag'
secrets: inherit
with:
jest-enabled: false
jest-enabled: true
jest-test-command: yarn run test:jest
bigtest-enabled: true
bigtest-test-command: xvfb-run --server-args="-screen 0 1024x768x24" yarn test $YARN_TEST_OPTIONS --karma.singleRun --karma.browsers ChromeDocker --karma.reporters mocha junit --coverage
sonar-sources: ./lib
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,7 @@ export { default as useCustomFields } from './lib/CustomFields/utils/useCustomFi
export { default as ProfilePicture } from './lib/ProfilePicture';
export { default as useProfilePicture } from './lib/ProfilePicture/utils';

export { default as LinkedUser } from './lib/LinkedUser';

export * from './lib/ColumnManager';
export * from './lib/utils';
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require('path');
const config = require('@folio/jest-config-stripes');

module.exports = {
...config,
setupFiles: [
...config.setupFiles,
path.join(__dirname, './tests/jest/setupFiles.js'),
],
};
32 changes: 32 additions & 0 deletions lib/LinkedUser/LinkedUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import PropTypes from 'prop-types';

import { useStripes } from '@folio/stripes-core';
import { getFullName } from '@folio/stripes-util';
import { TextLink } from '@folio/stripes-components';

/**
* LinkedUser
* Link to a user-details record if permissions allow,
* or return a plaintext name.
*/
const LinkedUser = ({ user, formatter = getFullName }) => {
const stripes = useStripes();

return stripes.hasPerm('ui-users.view') ? (
<TextLink to={`/users/preview/${user.id}?query=${user.username}`}>
{formatter(user)}
</TextLink>
) : (
<>{formatter(user)}</>
);
};

LinkedUser.propTypes = {
user: PropTypes.shape({
id: PropTypes.string,
username: PropTypes.string,
}),
formatter: PropTypes.func,
};

export default LinkedUser;
64 changes: 64 additions & 0 deletions lib/LinkedUser/LinkedUser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { render, screen } from '@folio/jest-config-stripes/testing-library/react';
import { useStripes } from '@folio/stripes-core';

Check warning on line 2 in lib/LinkedUser/LinkedUser.test.js

View workflow job for this annotation

GitHub Actions / ui / Install and lint / Install and lint

'useStripes' is defined but never used. Allowed unused vars must match /React/u

import LinkedUser from './LinkedUser';

const mockHasPerm = jest.fn();

jest.mock('stripes-config', () => (
{
modules: [],
metadata: {},
}
),
{ virtual: true });

jest.mock('@folio/stripes-core', () => ({
...jest.requireActual('@folio/stripes-core'),
useStripes: () => ({ hasPerm: mockHasPerm }),
}));

const userRecord = {
id: 'id',
username: 'username',
personal: {
firstName: 'first',
lastName: 'last',
middleName: 'middle',
}
};

describe('useLinkedUser', () => {
beforeEach(() => {
mockHasPerm.mockReturnValue(true);
});

afterAll(() => {
jest.clearAllMocks();
});

it('returns a link when permission is present', () => {
mockHasPerm.mockReturnValue(true);

const { container } = render(<LinkedUser user={userRecord} />);
expect(container.querySelector('a')).not.toBeNull();
screen.getByText(userRecord.personal.firstName, { exact: false });
});

it('returns plain text when permission is absent', () => {
mockHasPerm.mockReturnValue(false);

const { container } = render(<LinkedUser user={userRecord} />);
expect(container.querySelector('a')).toBeNull();
screen.getByText(userRecord.personal.firstName, { exact: false });
});

it('uses provided formatter', () => {
mockHasPerm.mockReturnValue(true);
const formatter = (u) => u.username;

const { container } = render(<LinkedUser user={userRecord} formatter={formatter} />);
expect(container.querySelector('a')).not.toBeNull();
screen.getByText(userRecord.username);
});
});
1 change: 1 addition & 0 deletions lib/LinkedUser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './LinkedUser';
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"lint": "eslint . && stylelint \"lib/**/*.css\"",
"eslint": "eslint .",
"stylelint": "stylelint \"lib/**/*.css\"",
"test": "stripes test karma",
"test:bigtest": "stripes test karma",
"test:jest": "jest --ci --coverage --colors",
"test": "yarn test:jest && yarn test:bigtest",
"test-dev": "stripes test karma --watch",
"build-mod-descriptor": "stripes mod descriptor --full --strict | jq '.[]' > module-descriptor.json ",
"formatjs-compile": "formatjs compile-folder --ast --format simple ./translations/stripes-smart-components ./translations/stripes-smart-components/compiled"
Expand All @@ -44,6 +46,7 @@
"@bigtest/mirage": "^0.0.1",
"@bigtest/mocha": "^0.5.1",
"@folio/eslint-config-stripes": "^7.0.0",
"@folio/jest-config-stripes": "^2.1.0",
"@folio/stripes-cli": "^3.2.1",
"@folio/stripes-components": "^12.0.0",
"@folio/stripes-connect": "^9.0.0",
Expand Down Expand Up @@ -95,10 +98,10 @@
"react-final-form": "^6.3.0",
"react-final-form-arrays": "^3.1.1",
"react-final-form-listeners": "^1.0.3",
"react-image": "^4.1.0",
"react-query": "^3.9.8",
"react-quill": "^2.0.0",
"react-router-prop-types": "^1.0.4",
"react-image": "^4.1.0",
"redux-form": "^8.3.0",
"uuid": "^9.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ requireTest.keys().forEach(requireTest);
turnOffWarnings();

// require all source files in lib for code coverage
const componentsContext = require.context('../lib/', true, /^(?!.*(stories|examples)).*\.js$/);
const componentsContext = require.context('../lib/', true, /^(?!.*(stories|examples|\.test\.)).*\.js$/);
componentsContext.keys().forEach(componentsContext);
6 changes: 6 additions & 0 deletions tests/jest/__mock__/__resizeObserver.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
window.ResizeObserver = jest.fn().mockImplementation(() => ({
disconnect: jest.fn(),
observe: jest.fn(),
unobserve: jest.fn(),
}));

1 change: 1 addition & 0 deletions tests/jest/__mock__/currencyData.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jest.mock('currency-codes/data', () => ({ filter: () => [] }));
8 changes: 8 additions & 0 deletions tests/jest/__mock__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import './stripesConfig.mock';
import './stripesCore.mock';
import './stripes.mock';
import './intl.mock';
import './stripesIcon.mock';
import './stripesComponents.mock';
import './currencyData.mock';
import './resizeObserver.mock';
61 changes: 61 additions & 0 deletions tests/jest/__mock__/intl.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

jest.mock('react-intl', () => {
const intl = {
formatMessage: ({ id }) => id,
formatNumber: (value) => value,
formatTime: (value) => value,
formatDisplayName: (value) => value,
formatDate: (value) => value,
formatDateToParts: jest.fn(() => ([
{
'type': 'month',
'value': '7'
},
{
'type': 'literal',
'value': '/'
},
{
'type': 'day',
'value': '31'
},
{
'type': 'literal',
'value': '/'
},
{
'type': 'year',
'value': '2024'
}
]
)),
};

return {
...jest.requireActual('react-intl'),
FormattedMessage: jest.fn(({ id, children }) => {
if (children) {
return children([id]);
}

return id;
}),
FormattedTime: jest.fn(({ value, children }) => {
if (children) {
return children([value]);
}

return value;
}),
FormattedDate: jest.fn(({ value, children }) => {
if (children) {
return children([value]);
}

return value;
}),
useIntl: () => intl,
injectIntl: (Component) => (props) => <Component {...props} intl={intl} />,
};
});
13 changes: 13 additions & 0 deletions tests/jest/__mock__/matchMedia.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
}))
});
9 changes: 9 additions & 0 deletions tests/jest/__mock__/reactFinalFormArrays.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

jest.mock('react-final-form-arrays', () => {
return {
...jest.requireActual('react-final-form-arrays'),

FieldArray: () => <div>FieldArray</div>,
};
});
9 changes: 9 additions & 0 deletions tests/jest/__mock__/reactFinalFormListeners.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

jest.mock('react-final-form-listeners', () => {
return {
...jest.requireActual('react-final-form-listeners'),

OnChange: jest.fn(({ children }) => <>{children()}</>),
};
});
5 changes: 5 additions & 0 deletions tests/jest/__mock__/resizeObserver.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
26 changes: 26 additions & 0 deletions tests/jest/__mock__/stripes.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { noop } from 'lodash';

const buildStripes = (otherProperties = {}) => ({
hasPerm: noop,
hasInterface: noop,
clone: noop,
logger: { log: noop },
config: {},
okapi: {
url: '',
tenant: '',
},
locale: 'en-US',
withOkapi: true,
setToken: noop,
actionNames: [],
setLocale: noop,
setTimezone: noop,
setCurrency: noop,
setSinglePlugin: noop,
setBindings: noop,
connect: noop,
...otherProperties,
});

export default buildStripes;
Loading

0 comments on commit e7b7764

Please sign in to comment.