diff --git a/src/apps/gigs/README.md b/src/apps/gigs/README.md index 9d40a7d0d..979a00c47 100644 --- a/src/apps/gigs/README.md +++ b/src/apps/gigs/README.md @@ -6,6 +6,8 @@ This sub-app ports the public community-app Gig Work flow into platform-ui: featured-first created/updated ordering, hotlist and ten jobs per result page. - `/gigs/:slug`: compensation, location, duration, weekly hours, timezone, required skills, description, eligibility notes and application handoff. + Profile, forum and opportunity advice links open in new tabs, preserving the + gig details page. Gig navigation and email links keep their existing behavior. - `/gigs/:slug/apply`: sign-in with the full return URL, candidate prefill, resume upload, skill autocomplete/custom skills, weekly pay expectation, referral source, availability confirmations, policy dialogs and application @@ -60,12 +62,15 @@ yarn build yarn test:no-watch --runInBand --watch=false --runTestsByPath \ src/apps/gigs/src/gigs.utils.spec.ts \ src/apps/gigs/src/gigs.service.spec.ts \ + src/apps/gigs/src/pages/GigDetailsPage.spec.tsx \ src/apps/gigs/src/components/GigApplicationForm.spec.tsx ``` The tests cover discovery rules, salary fallbacks, required fields, consent and availability, upload limits, legacy payload mapping, HTTP-200 error envelopes, expired authentication, prefill, submission retry and already-placed candidates. +Gig detail link tests cover new-tab advice links for signed-in and anonymous +visitors, plus existing gig navigation and email destinations. Also verify the listing, detail and anonymous apply route against real Recruit reads in a browser at desktop and mobile widths. Authenticated submission tests use mocks so verification does not create real candidates or send recruiter diff --git a/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx b/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx new file mode 100644 index 000000000..52fe29d2a --- /dev/null +++ b/src/apps/gigs/src/pages/GigDetailsPage.spec.tsx @@ -0,0 +1,114 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { MemoryRouter, Route, Routes } from 'react-router-dom' +import useSWR from 'swr' + +import { render, screen } from '@testing-library/react' +import { profileContext, ProfileContextData, UserProfile } from '~/libs/core' +import '@testing-library/jest-dom' + +import { Gig } from '../models' + +import GigDetailsPage from './GigDetailsPage' + +jest.mock('swr') +jest.mock('~/config', () => ({ + EnvironmentConfig: { + TC_DOMAIN: 'topcoder-dev.com', + URLS: { ACCOUNT_SETTINGS: 'https://accounts.topcoder-dev.com/settings' }, + USER_PROFILE_URL: 'https://profiles.topcoder-dev.com', + }, +}), { virtual: true }) +jest.mock('~/libs/core', () => { + const ReactModule: typeof import('react') = jest.requireActual('react') + return { profileContext: ReactModule.createContext({}) } +}, { virtual: true }) +jest.mock('~/libs/ui', () => ({ + PageTitle: (): JSX.Element => <>, +}), { virtual: true }) +jest.mock('../gigs.service', () => ({ + ...jest.requireActual('../gigs.service'), + getGig: jest.fn(), +})) +jest.mock('../components/GigShared', () => ({ + GigContent: (props: { text: string }): JSX.Element =>
{props.text}
, + GigFacts: (): JSX.Element => <>, + GigState: (props: { title: string }): JSX.Element =>
{props.title}
, +})) + +const job: Gig = { + enable_job_application_form: 1, + job_description_text: 'Test job description', + job_status: { id: 1 }, + name: 'Test gig', + slug: 'test-gig', +} + +/** + * Renders the gig details route with an optional member handle for navigation regression tests. + * + * @param handle Signed-in member handle; omitted to render the anonymous profile destination. + * @returns Nothing; assertions query the rendered document. + * @throws Propagates rendering errors so the test fails. + */ +function renderDetails(handle?: string): void { + render( + + + + } /> + + + , + ) +} + +describe('Gig details links', () => { + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(useSWR) + .mockReturnValue({ data: job, error: undefined, isValidating: false, mutate: jest.fn() }) + }) + + it.each([ + ['qa member', 'https://profiles.topcoder-dev.com/qa%20member'], + [undefined, 'https://accounts.topcoder-dev.com/settings'], + ])('opens advice links in new tabs for member handle %s', (handle, profileUrl) => { + renderDetails(handle) + + const links = [ + ['Update your profile', profileUrl], + ['Visit the Gig Work forum', 'https://vanilla.topcoder-dev.com/categories/gig-work-discusssions'], + ['Browse opportunities', '/opportunities'], + ] + links.forEach(([name, href]) => { + const link = screen.getByRole('link', { name }) + expect(link) + .toHaveAttribute('href', href) + expect(link) + .toHaveAttribute('target', '_blank') + expect(link) + .toHaveAttribute('rel', 'noopener noreferrer') + }) + }) + + it('preserves gig navigation and email destinations', () => { + renderDetails() + + const links = [ + ['← All gigs', '/gigs'], + ['Apply to this job', '/gigs/test-gig/apply'], + ['View other gigs', '/gigs'], + ['talent.taas@wipro.com', 'mailto:talent.taas@wipro.com'], + ['Contact the Gig Work team', 'mailto:talent.taas@wipro.com'], + ] + links.forEach(([name, href]) => { + const link = screen.getByRole('link', { name }) + expect(link) + .toHaveAttribute('href', href) + expect(link) + .not.toHaveAttribute('target') + }) + }) +}) diff --git a/src/apps/gigs/src/pages/GigDetailsPage.tsx b/src/apps/gigs/src/pages/GigDetailsPage.tsx index 56ee5e74e..7eac867f0 100644 --- a/src/apps/gigs/src/pages/GigDetailsPage.tsx +++ b/src/apps/gigs/src/pages/GigDetailsPage.tsx @@ -12,7 +12,10 @@ import { GigContent, GigFacts, GigState } from '../components/GigShared' import { getGig, RecruitError } from '../gigs.service' import { gigSkills, GIGS_PATH, isOpenGig } from '../gigs.utils' -/** Shows job facts, description, eligibility notes and the application handoff, including closed/error states. */ +/** + * Shows job facts, description, eligibility notes and the application handoff, including closed/error states. + * Opens profile, forum and opportunity advice links in new tabs so members can keep the gig details open. + */ const GigDetailsPage: FC = () => { const { slug = '' }: { slug?: string } = useParams<{ slug: string }>() const { profile }: ProfileContextData = useContext(profileContext) @@ -105,6 +108,8 @@ const GigDetailsPage: FC = () => { )}` : EnvironmentConfig.URLS.ACCOUNT_SETTINGS } + target='_blank' + rel='noopener noreferrer' > Update your profile @@ -115,6 +120,8 @@ const GigDetailsPage: FC = () => { Visit the Gig Work forum @@ -124,7 +131,9 @@ const GigDetailsPage: FC = () => {

Participate in Topcoder competitions to demonstrate what you can do.

- Browse opportunities + + Browse opportunities +