Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/components/WorkspaceConfirmationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import useThemeStyles from '@hooks/useThemeStyles';
import useWorkspaceConfirmationAvatar from '@hooks/useWorkspaceConfirmationAvatar';
import {clearDraftValues} from '@libs/actions/FormActions';
import {generateDefaultWorkspaceName, generatePolicyID} from '@libs/actions/Policy/Policy';
import {generatePolicyID, newGenerateDefaultWorkspaceName} from '@libs/actions/Policy/Policy';
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
import {addErrorMessage} from '@libs/ErrorUtils';
import getFirstAlphaNumericCharacter from '@libs/getFirstAlphaNumericCharacter';
Expand All @@ -21,6 +21,7 @@
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import AvatarWithImagePicker from './AvatarWithImagePicker';
Expand Down Expand Up @@ -114,7 +115,9 @@
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [draftValues] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM_DRAFT);

const defaultWorkspaceName = generateDefaultWorkspaceName(policyOwnerEmail || session?.email);
const email = policyOwnerEmail || session?.email || '';

Check failure on line 118 in src/components/WorkspaceConfirmationForm.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 118 in src/components/WorkspaceConfirmationForm.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 118 in src/components/WorkspaceConfirmationForm.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 118 in src/components/WorkspaceConfirmationForm.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator
const lastWorkspaceNumber = lastWorkspaceNumberSelector(policies, email);
const defaultWorkspaceName = newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber);
const [workspaceNameFirstCharacter, setWorkspaceNameFirstCharacter] = useState(defaultWorkspaceName ?? '');

const userCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD;
Expand Down
15 changes: 15 additions & 0 deletions src/hooks/useLastWorkspaceNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type {OnyxCollection} from 'react-native-onyx';
import useOnyx from '@hooks/useOnyx';

Check warning on line 2 in src/hooks/useLastWorkspaceNumber.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected subpath import via alias '@hooks/useOnyx'. Use './useOnyx' instead

Check warning on line 2 in src/hooks/useLastWorkspaceNumber.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected subpath import via alias '@hooks/useOnyx'. Use './useOnyx' instead

Check warning on line 2 in src/hooks/useLastWorkspaceNumber.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected subpath import via alias '@hooks/useOnyx'. Use './useOnyx' instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issues @bernhardoj

import ONYXKEYS from '@src/ONYXKEYS';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import {emailSelector} from '@src/selectors/Session';
import type {Policy} from '@src/types/onyx';

function useLastWorkspaceNumber() {
const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector});
const lastWorkspaceNumberSelectorWithEmail = (policies: OnyxCollection<Policy>) => lastWorkspaceNumberSelector(policies, sessionEmail ?? '');
const [lastWorkspaceNumber] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: lastWorkspaceNumberSelectorWithEmail});
return lastWorkspaceNumber;
}

export default useLastWorkspaceNumber;
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ function AuthScreensInitHandler() {
const [initialLastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});

const [lastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT);
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [conciergeReportID, conciergeReportIDMetadata] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
Expand Down
49 changes: 49 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
};

const deprecatedAllPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 242 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 242 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -255,7 +255,7 @@
});

let deprecatedAllReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 258 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 258 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -265,7 +265,7 @@

let deprecatedSessionEmail = '';
let deprecatedSessionAccountID = 0;
Onyx.connect({

Check warning on line 268 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 268 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
deprecatedSessionEmail = val?.email ?? '';
Expand All @@ -274,7 +274,7 @@
});

let deprecatedAllPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 277 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Check warning on line 277 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (deprecatedAllPersonalDetails = val),
});
Expand Down Expand Up @@ -2135,6 +2135,53 @@
Onyx.set(ONYXKEYS.DUPLICATE_WORKSPACE, {});
}

function getDisplayNameForWorkspace(email: string) {
const emailParts = email.split('@');
const domain = emailParts.at(1) ?? '';
const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN;
if (isSMSDomain) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('workspace.new.myGroupWorkspace', {});
}

if (!PUBLIC_DOMAINS_SET.has(domain.toLowerCase())) {
return Str.UCFirst(domain.split('.').at(0) ?? '');
}

const userDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email);
const displayName = userDetails?.displayName?.trim();
if (displayName) {
return Str.UCFirst(displayName);
}

const username = emailParts.at(0) ?? '';
return Str.UCFirst(username);
}

/**
* Generate a policy name based on an email and policy list.
* @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead
* @param [lastWorkspaceNumber] the last workspace number
*/
function newGenerateDefaultWorkspaceName(email: string, lastWorkspaceNumber: number | undefined): string {
const emailParts = email ? email.split('@') : deprecatedSessionEmail.split('@');
if (!emailParts || emailParts.length !== 2) {
return '';
}
const domain = emailParts.at(1) ?? '';
const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN;

if (isSMSDomain) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('workspace.new.myGroupWorkspace', {workspaceNumber: lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined});
}

const displayNameForWorkspace = getDisplayNameForWorkspace(email || deprecatedSessionEmail);

// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('workspace.new.workspaceName', displayNameForWorkspace, lastWorkspaceNumber !== undefined ? lastWorkspaceNumber + 1 : undefined);
}

/**
* Generate a policy name based on an email and policy list.
* @param [email] the email to base the workspace name on. If not passed, will use the logged-in user's email instead
Expand Down Expand Up @@ -7081,6 +7128,8 @@
updateLastAccessedWorkspace,
clearDeleteWorkspaceError,
setWorkspaceDefaultSpendCategory,
getDisplayNameForWorkspace,
newGenerateDefaultWorkspaceName,
generateDefaultWorkspaceName,
updateGeneralSettings,
deleteWorkspaceAvatar,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import {isRequiredFulfilled} from '@libs/ValidationUtils';
import {clearWorkspaceDetailsDraft} from '@userActions/Onboarding';
import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy';
import {createWorkspace, generatePolicyID, newGenerateDefaultWorkspaceName} from '@userActions/Policy/Policy';
import {setOnboardingAdminsChatReportID, setOnboardingErrorMessage, setOnboardingPolicyID} from '@userActions/Welcome';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import INPUT_IDS from '@src/types/form/WorkspaceConfirmationForm';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import OnboardingCurrencyPicker from './OnboardingCurrencyPicker';
Expand Down Expand Up @@ -57,7 +58,9 @@ function BaseOnboardingWorkspaceConfirmation({shouldUseNativeStyles}: BaseOnboar

const paidGroupPolicy = Object.values(allPolicies ?? {}).find((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email));

const defaultWorkspaceName = draftValues?.name ?? generateDefaultWorkspaceName(session?.email);
const email = session?.email ?? '';
const lastWorkspaceNumber = lastWorkspaceNumberSelector(allPolicies, email);
const defaultWorkspaceName = draftValues?.name ?? newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber);
const defaultCurrency = draftValues?.currency ?? currentUserPersonalDetails?.localCurrencyCode ?? CONST.CURRENCY.USD;

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnbo
import Navigation from '@libs/Navigation/Navigation';
import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import {getSubscriptionPrice} from '@libs/SubscriptionUtils';
import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy';
import {createWorkspace, generatePolicyID, newGenerateDefaultWorkspaceName} from '@userActions/Policy/Policy';
import {completeOnboarding as completeOnboardingReport} from '@userActions/Report';
import {setOnboardingAdminsChatReportID, setOnboardingErrorMessage, setOnboardingPolicyID} from '@userActions/Welcome';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import type {OnboardingPurpose} from '@src/types/onyx';
import type IconAsset from '@src/types/utils/IconAsset';
import type {BaseOnboardingWorkspaceOptionalProps} from './types';
Expand Down Expand Up @@ -158,11 +159,13 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding
const paidGroupPolicy = Object.values(allPolicies ?? {}).find((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email));
const shouldCreateWorkspace = !onboardingPolicyID && !paidGroupPolicy;

const email = session?.email ?? '';
const lastWorkspaceNumber = lastWorkspaceNumberSelector(allPolicies, email);
const {adminsChatReportID, policyID} = shouldCreateWorkspace
? createWorkspace({
policyOwnerEmail: undefined,
makeMeAdmin: true,
policyName: generateDefaultWorkspaceName(session?.email),
policyName: newGenerateDefaultWorkspaceName(email, lastWorkspaceNumber),
policyID: generatePolicyID(),
engagementChoice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE,
currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
Expand Down
30 changes: 30 additions & 0 deletions src/selectors/Policy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import escapeRegExp from 'lodash/escapeRegExp';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {getDisplayNameForWorkspace} from '@libs/actions/Policy/Policy';
import {translate} from '@libs/Localize';
import {areAllGroupPoliciesExpenseChatDisabled, getActiveAdminWorkspaces, getOwnedPaidPolicies, isPaidGroupPolicy, shouldShowPolicy} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import type {Policy, PolicyReportField} from '@src/types/onyx';
Expand Down Expand Up @@ -134,6 +137,32 @@ const hasPoliciesConnectedToSageIntacctSelector = (policies: OnyxCollection<Poli

const hasPoliciesConnectedToNetSuiteSelector = (policies: OnyxCollection<Policy>) => !!adminPoliciesConnectedToNetSuiteSelector(policies).length;

function lastWorkspaceNumberSelector(policies: OnyxCollection<Policy>, email: string): number | undefined {
const emailParts = email.split('@');
if (emailParts.length !== 2) {
return undefined;
}

const displayNameForWorkspace = getDisplayNameForWorkspace(email);
// find default named workspaces and increment the last number
const escapedName = escapeRegExp(displayNameForWorkspace);
const workspaceTranslations = Object.values(CONST.LOCALES)
.map((lang) => translate(lang, 'workspace.common.workspace'))
.join('|');

const domain = emailParts.at(1) ?? '';
const isSMSDomain = `@${domain}` === CONST.SMS.DOMAIN;
const workspaceRegex = isSMSDomain ? new RegExp(`^${escapedName}\\s*(\\d+)?$`, 'i') : new RegExp(`^(?=.*${escapedName})(?:.*(?:${workspaceTranslations})\\s*(\\d+)?)`, 'i');

const workspaceNumbers = Object.values(policies ?? {})
.map((policy) => workspaceRegex.exec(policy?.name ?? ''))
.filter(Boolean) // Remove null matches
.map((match) => Number(match?.[1] ?? '0'));
const lastWorkspaceNumber = workspaceNumbers.length > 0 ? Math.max(...workspaceNumbers) : undefined;

return lastWorkspaceNumber;
}

export {
activePolicySelector,
createAllPolicyReportFieldsSelector,
Expand All @@ -150,4 +179,5 @@ export {
adminPoliciesConnectedToNetSuiteSelector,
hasPoliciesConnectedToSageIntacctSelector,
hasPoliciesConnectedToNetSuiteSelector,
lastWorkspaceNumberSelector,
};
14 changes: 7 additions & 7 deletions tests/actions/PolicyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@ describe('actions/Policy', () => {
await waitForBatchedUpdates();

const policyID = Policy.generatePolicyID();
const expectedName = Policy.generateDefaultWorkspaceName(ESH_EMAIL);
const expectedName = Policy.newGenerateDefaultWorkspaceName(ESH_EMAIL, undefined);

Policy.createDraftInitialWorkspace({choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, ESH_EMAIL, '', policyID, false);
await waitForBatchedUpdates();
Expand Down Expand Up @@ -3001,7 +3001,7 @@ describe('actions/Policy', () => {
accountID: TEST_ACCOUNT_ID,
});

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_NON_PUBLIC_DOMAIN_EMAIL, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace));
});

Expand All @@ -3014,7 +3014,7 @@ describe('actions/Policy', () => {
accountID: TEST_ACCOUNT_ID,
});

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace));
});

Expand All @@ -3029,7 +3029,7 @@ describe('actions/Policy', () => {
accountID: TEST_ACCOUNT_ID,
});

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL_2);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL_2, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', displayNameForWorkspace));
});

Expand All @@ -3047,7 +3047,7 @@ describe('actions/Policy', () => {

await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies);

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2));
});

Expand All @@ -3058,7 +3058,7 @@ describe('actions/Policy', () => {
accountID: TEST_ACCOUNT_ID,
});

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_SMS_DOMAIN_EMAIL, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.myGroupWorkspace', {}));
});

Expand All @@ -3080,7 +3080,7 @@ describe('actions/Policy', () => {

await Onyx.set(ONYXKEYS.COLLECTION.POLICY, existingPolicies);

const workspaceName = Policy.generateDefaultWorkspaceName(TEST_EMAIL);
const workspaceName = Policy.newGenerateDefaultWorkspaceName(TEST_EMAIL, undefined);
expect(workspaceName).toBe(TestHelper.translateLocal('workspace.new.workspaceName', TEST_DISPLAY_NAME, 2));
});
});
Expand Down
60 changes: 60 additions & 0 deletions tests/unit/PolicySelectorTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import type {Policy} from '@src/types/onyx';

describe('lastWorkspaceNumberSelector', () => {
const email = 'jdoe@expensify.com';
const displayName = 'Expensify';
const workspaceName = `${displayName} Workspace`;

it('should return undefined when there are no policies', () => {
expect(lastWorkspaceNumberSelector({}, email)).toBeUndefined();
});

it('should return undefined when email is invalid', () => {
expect(lastWorkspaceNumberSelector({}, 'invalid-email')).toBeUndefined();
});

it('should return 0 when there is a matching workspace without a number', () => {
const policies = {
[`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName} as Policy,
};
expect(lastWorkspaceNumberSelector(policies, email)).toBe(0);
});

it('should return the number when there is a matching workspace with a number', () => {
const policies = {
[`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: `${workspaceName} 2`} as Policy,
};
expect(lastWorkspaceNumberSelector(policies, email)).toBe(2);
});

it('should return the maximum number when there are multiple matching workspaces', () => {
const policies = {
[`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName} as Policy,
[`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${workspaceName} 2`} as Policy,
[`${ONYXKEYS.COLLECTION.POLICY}3`]: {name: `${workspaceName} 5`} as Policy,
[`${ONYXKEYS.COLLECTION.POLICY}4`]: {name: 'Other Workspace'} as Policy,
};
expect(lastWorkspaceNumberSelector(policies, email)).toBe(5);
});

it('should handle SMS domain correctly', () => {
const smsEmail = `+15555555555${CONST.SMS.DOMAIN}`;
const smsDisplayName = 'My Group Workspace';
const policies = {
[`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: smsDisplayName} as Policy,
[`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${smsDisplayName} 3`} as Policy,
};
expect(lastWorkspaceNumberSelector(policies, smsEmail)).toBe(3);
});

it('should ignore case when matching workspace names', () => {
const policies = {
[`${ONYXKEYS.COLLECTION.POLICY}1`]: {name: workspaceName.toLowerCase()} as Policy,
[`${ONYXKEYS.COLLECTION.POLICY}2`]: {name: `${workspaceName.toUpperCase()} 4`} as Policy,
};
expect(lastWorkspaceNumberSelector(policies, email)).toBe(4);
});
});
55 changes: 55 additions & 0 deletions tests/unit/useLastWorkspaceNumberTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {renderHook} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy} from '@src/types/onyx';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

describe('useLastWorkspaceNumber', () => {
const email = 'jdoe@expensify.com';
const displayName = 'Expensify';
const workspaceName = `${displayName} Workspace`;

beforeAll(() => {
Onyx.init({keys: ONYXKEYS});
});

beforeEach(() => {
Onyx.clear();
return waitForBatchedUpdates();
});

it('should return the correct last workspace number from Onyx', async () => {
await Onyx.merge(ONYXKEYS.SESSION, {email});
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: `${workspaceName} 3`} as Policy);
await waitForBatchedUpdates();

const {result} = renderHook(() => useLastWorkspaceNumber());

expect(result.current).toBe(3);
});

it('should update when Onyx data changes', async () => {
await Onyx.merge(ONYXKEYS.SESSION, {email});
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: `${workspaceName} 3`} as Policy);
await waitForBatchedUpdates();

const {result} = renderHook(() => useLastWorkspaceNumber());
expect(result.current).toBe(3);

await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}2`, {name: `${workspaceName} 5`} as Policy);
await waitForBatchedUpdates();

expect(result.current).toBe(5);
});

it('should return undefined if no matching workspaces exist', async () => {
await Onyx.merge(ONYXKEYS.SESSION, {email});
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}1`, {name: 'Other Workspace'} as Policy);
await waitForBatchedUpdates();

const {result} = renderHook(() => useLastWorkspaceNumber());

expect(result.current).toBeUndefined();
});
});
Loading