From 9bd4c1b735ac7cc2b9cac02910bccea72eebfc6b Mon Sep 17 00:00:00 2001 From: atomicgamedeveloper <109801255+atomicgamedeveloper@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:57:56 +0200 Subject: [PATCH] Update Account tabs and add group list (#838) - Uses the existing user profile information obtained in OAuth signin process and updates the profile page. - Downgrades the ESLint version to make it compatible with eslintrc configuration file. --- client/package.json | 2 +- client/src/route/auth/Account.tsx | 3 +- client/src/route/auth/AccountTabData.tsx | 92 +++++++++++++- client/test/unitTests/Routes/Account.test.tsx | 113 ++++++++++++++++-- client/yarn.lock | 91 +------------- 5 files changed, 200 insertions(+), 101 deletions(-) diff --git a/client/package.json b/client/package.json index 93a2190d3..768bc4214 100644 --- a/client/package.json +++ b/client/package.json @@ -51,7 +51,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "dotenv": "^16.1.4", - "eslint": "^8.54.0", + "eslint": "8.54.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.29.0", diff --git a/client/src/route/auth/Account.tsx b/client/src/route/auth/Account.tsx index 352897655..6b2623ff6 100644 --- a/client/src/route/auth/Account.tsx +++ b/client/src/route/auth/Account.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import Layout from 'page/Layout'; -import { Typography } from '@mui/material'; import TabComponent from 'components/tab/TabComponent'; import { TabData } from 'components/tab/subcomponents/TabRender'; import tabs from './AccountTabData'; @@ -8,7 +7,7 @@ import tabs from './AccountTabData'; function AccountContent() { const AccountTab: TabData[] = tabs.map((tab) => ({ label: tab.label, - body: {tab.body}, + body: tab.body, })); return ( diff --git a/client/src/route/auth/AccountTabData.tsx b/client/src/route/auth/AccountTabData.tsx index e072642fa..a2b6a8bf1 100644 --- a/client/src/route/auth/AccountTabData.tsx +++ b/client/src/route/auth/AccountTabData.tsx @@ -1,13 +1,97 @@ -import { ITabs } from 'route/IData'; +import * as React from 'react'; +import { useAuth } from 'react-oidc-context'; +import { TabData } from 'components/tab/subcomponents/TabRender'; -const tabs: ITabs[] = [ +function ListGroups(groups: string[]): React.ReactNode[] { + const boldGroups = groups.map((group) => + React.createElement('b', null, group), + ); + + const userBelongsToOneGroup = groups.length === 1; + if (userBelongsToOneGroup) { + return boldGroups; + } + + const groupListing: React.ReactNode[] = []; + boldGroups + .slice(0, -1) + .map((groupElement) => groupListing.push(groupElement, ', ')); + groupListing.splice(groupListing.length - 1, 1, [ + ' and ', + boldGroups.slice(-1), + ]); + return groupListing; +} + +function GroupParagraph(groups: string[], name: React.ReactNode) { + const userBelongsToAnyGroups = groups.length > 0; + if (!userBelongsToAnyGroups) { + return ( +

+ {name} does not belong to any groups. +

+ ); + } + + const groupListing = ListGroups(groups); + const groupSuffix = groups.length > 1 ? 's' : ''; + return ( +

+ {name} belongs to {React.Children.toArray(groupListing)} group + {groupSuffix}. +

+ ); +} + +function ProfileTab() { + const { user } = useAuth(); + const name = (user?.profile.preferred_username as string | undefined) ?? ''; + const pfp = user?.profile.picture; + const profileUrl = user?.profile.profile; + + const groups = (user?.profile.groups as string[] | string | undefined) ?? []; + const isGroupsAString = typeof groups === 'string'; + const groupsArray = isGroupsAString ? [groups] : groups; + const groupParagraph = GroupParagraph(groupsArray, name); + + return ( +
+

Profile

+ +

+ The username is {name}. See more details on{' '} + + SSO OAuth Provider. + +

+ {groupParagraph} +
+ ); +} + +function SettingsTab() { + const profileUrl = useAuth().user?.profile.profile; + return ( +
+

Settings

+

+ Edit the profile on{' '} + + SSO OAuth Provider. + +

+
+ ); +} + +const tabs: TabData[] = [ { label: 'Profile', - body: `Profile - potentially visible to other users.`, + body: , }, { label: 'Settings', - body: `Account settings - private to a user.`, + body: , }, ]; diff --git a/client/test/unitTests/Routes/Account.test.tsx b/client/test/unitTests/Routes/Account.test.tsx index e8cf91b2c..3204e40a1 100644 --- a/client/test/unitTests/Routes/Account.test.tsx +++ b/client/test/unitTests/Routes/Account.test.tsx @@ -1,12 +1,111 @@ import * as React from 'react'; import Account from 'route/auth/Account'; -import tabs from 'route/auth/AccountTabData'; -import { InitRouteTests, itDisplaysContentOfTabs } from '../testUtils'; +import { render, screen } from '@testing-library/react'; +import { useAuth } from 'react-oidc-context'; -describe('Account Page', () => { - const tabLabels: string[] = []; - tabs.forEach((tab) => tabLabels.push(tab.label)); - InitRouteTests(); +jest.mock('react-oidc-context'); - itDisplaysContentOfTabs(tabs); +describe('AccountTabs', () => { + type Profile = { + preferred_username: string | undefined; + picture: string | undefined; + profile: string | undefined; + groups: string[] | string | undefined; + }; + + const mockProfile: Profile = { + preferred_username: 'user1', + picture: 'pfp.jpg', + profile: 'test.com', + groups: 'group-one', + }; + + function setupTest(groups: string[] | string) { + mockProfile.groups = groups; + const mockuser = { profile: mockProfile }; + (useAuth as jest.Mock).mockReturnValue({ + user: mockuser, + }); + render(); + } + + afterEach(() => { + jest.clearAllMocks(); + }); + + function testStaticElements() { + const profilePicture = screen.getByTestId('profile-picture'); + expect(profilePicture).toBeInTheDocument(); + expect(profilePicture).toHaveAttribute('src', 'pfp.jpg'); + + const username = screen.getAllByText('user1'); + expect(username).not.toBeNull(); + expect(username).toHaveLength(2); + + const profileLink = screen.getByRole('link', { + name: /SSO OAuth Provider/i, + }); + expect(profileLink).toBeInTheDocument(); + expect(profileLink).toHaveAttribute('href', 'test.com'); + } + + test('renders AccountTabs with correct profile information when user is in 0 groups', () => { + setupTest([]); + + testStaticElements(); + + const groupInfo = screen.getByText(/belong to/); + expect(groupInfo).toHaveProperty( + 'innerHTML', + 'user1 does not belong to any groups.', + ); + }); + + test('renders AccountTabs with correct profile information when user is in 1 group', () => { + setupTest('group-one'); + + testStaticElements(); + + const groupInfo = screen.getByText(/belongs to/); + expect(groupInfo).toHaveProperty( + 'innerHTML', + 'user1 belongs to group-one group.', + ); + }); + + test('renders AccountTabs with correct profile information when user is in 2 groups', () => { + setupTest(['first-group', 'second-group']); + + testStaticElements(); + + const groupInfo = screen.getByText(/belongs to/); + expect(groupInfo).toHaveProperty( + 'innerHTML', + 'user1 belongs to first-group and second-group groups.', + ); + }); + + test('renders AccountTabs with correct profile information when user is in 3 groups', () => { + setupTest(['group1', 'group2', 'group3']); + + testStaticElements(); + + const groupInfo = screen.getByText(/belongs to/); + expect(groupInfo).toHaveProperty( + 'innerHTML', + 'user1 belongs to group1, group2 and group3 groups.', + ); + }); + + test('renders AccountTabs with correct profile information when user is in more than 3 groups', () => { + setupTest(['g1', 'g2', 'g3', 'g4', 'g5']); + + testStaticElements(); + + const groupInfo = screen.getByText(/belongs to/); + expect(groupInfo).toHaveProperty( + 'innerHTML', + 'user1 belongs to g1, g2, g3, g4 and g5 groups.', + ); + }); }); diff --git a/client/yarn.lock b/client/yarn.lock index e648c3841..869742b40 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -4064,16 +4064,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.4.7: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" @@ -4178,15 +4168,6 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5198,7 +5179,7 @@ eslint-webpack-plugin@^3.1.1: normalize-path "^3.0.0" schema-utils "^4.0.0" -eslint@^8.3.0, eslint@^8.54.0: +eslint@8.54.0, eslint@^8.3.0: version "8.54.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.54.0.tgz#588e0dd4388af91a2e8fa37ea64924074c783537" integrity sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA== @@ -7689,14 +7670,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -8163,11 +8136,6 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" -os-shim@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" - integrity sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A== - p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -8921,15 +8889,6 @@ postcss@^8.3.5, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.31, postcss@^8.4. picocolors "^1.0.0" source-map-js "^1.0.2" -pre-commit@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" - integrity sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA== - dependencies: - cross-spawn "^5.0.1" - spawn-sync "^1.0.15" - which "1.2.x" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9023,11 +8982,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -9315,7 +9269,7 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" -readable-stream@^2.0.1, readable-stream@^2.2.2: +readable-stream@^2.0.1: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -9861,13 +9815,6 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -9875,11 +9822,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -10007,14 +9949,6 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -spawn-sync@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" - integrity sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw== - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" - spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -10688,11 +10622,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - typescript@^4.9.5: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" @@ -11150,14 +11079,7 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" -which@1.2.x: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" - integrity sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw== - dependencies: - isexe "^2.0.0" - -which@^1.2.9, which@^1.3.1: +which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -11423,11 +11345,6 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -11487,4 +11404,4 @@ yargs@^17.3.1: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== \ No newline at end of file + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==