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 (
+
+ );
+}
+
+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==