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
5 changes: 5 additions & 0 deletions src/apps/gigs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions src/apps/gigs/src/pages/GigDetailsPage.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 => <div>{props.text}</div>,
GigFacts: (): JSX.Element => <></>,
GigState: (props: { title: string }): JSX.Element => <div>{props.title}</div>,
}))

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(
<profileContext.Provider
value={{ profile: handle ? { handle } as UserProfile : undefined } as ProfileContextData}
>
<MemoryRouter initialEntries={['/gigs/test-gig']}>
<Routes>
<Route path='/gigs/:slug' element={<GigDetailsPage />} />
</Routes>
</MemoryRouter>
</profileContext.Provider>,
)
}

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'],
['[email protected]', 'mailto:[email protected]'],
['Contact the Gig Work team', 'mailto:[email protected]'],
]
links.forEach(([name, href]) => {
const link = screen.getByRole('link', { name })
expect(link)
.toHaveAttribute('href', href)
expect(link)
.not.toHaveAttribute('target')
})
})
})
13 changes: 11 additions & 2 deletions src/apps/gigs/src/pages/GigDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -105,6 +108,8 @@ const GigDetailsPage: FC = () => {
)}`
: EnvironmentConfig.URLS.ACCOUNT_SETTINGS
}
target='_blank'
rel='noopener noreferrer'
>
Update your profile
</a>
Expand All @@ -115,6 +120,8 @@ const GigDetailsPage: FC = () => {
<a
href={`https://vanilla.${EnvironmentConfig.TC_DOMAIN}`
+ '/categories/gig-work-discusssions'}
target='_blank'
rel='noopener noreferrer'
>
Visit the Gig Work forum
</a>
Expand All @@ -124,7 +131,9 @@ const GigDetailsPage: FC = () => {
<p>
Participate in Topcoder competitions to demonstrate what you can do.
</p>
<Link to='/opportunities'>Browse opportunities</Link>
<Link to='/opportunities' target='_blank' rel='noopener noreferrer'>
Browse opportunities
</Link>
</li>
</ol>
<p>
Expand Down
Loading