diff --git a/src/app-router.ts b/src/app-router.ts index 98c5e4ed1..8f9c26014 100644 --- a/src/app-router.ts +++ b/src/app-router.ts @@ -1536,7 +1536,22 @@ const router: P.Parser writeReviewCompetingInterests(id)), + P.map(({ id }) => + pipe( + RM.of({ id }), + RM.apS( + 'body', + RM.gets(c => c.getBody()), + ), + RM.apS( + 'method', + RM.gets(c => c.getMethod()), + ), + RM.apS('user', maybeGetUser), + RM.bindW('response', RM.fromReaderTaskK(writeReviewCompetingInterests)), + RM.ichainW(handleResponse), + ), + ), ), pipe( writeReviewConductMatch.parser, diff --git a/src/write-review/competing-interests-page/index.ts b/src/write-review/competing-interests-page/index.ts index 3b55d48eb..8a60c509e 100644 --- a/src/write-review/competing-interests-page/index.ts +++ b/src/write-review/competing-interests-page/index.ts @@ -1,119 +1,125 @@ import { format } from 'fp-ts-routing' import * as E from 'fp-ts/lib/Either.js' -import * as I from 'fp-ts/lib/Identity.js' -import { flow, identity, pipe } from 'fp-ts/lib/function.js' -import { Status } from 'hyper-ts' -import * as RM from 'hyper-ts/lib/ReaderMiddleware.js' +import * as RT from 'fp-ts/lib/ReaderTask.js' +import * as RTE from 'fp-ts/lib/ReaderTaskEither.js' +import { identity, pipe } from 'fp-ts/lib/function.js' +import { StatusCodes } from 'http-status-codes' import * as D from 'io-ts/lib/Decoder.js' import { get } from 'spectacles-ts' import { P, match } from 'ts-pattern' import { type MissingE, hasAnError, missingE } from '../../form.js' -import { html, plainText, rawHtml, sendHtml } from '../../html.js' +import { html, plainText, rawHtml } from '../../html.js' +import { havingProblemsPage, pageNotFound } from '../../http-error.js' import { DefaultLocale, type SupportedLocale, translate } from '../../locales/index.js' -import { getMethod, notFound, seeOther, serviceUnavailable } from '../../middleware.js' -import { templatePage } from '../../page.js' -import { type PreprintTitle, getPreprintTitle } from '../../preprint.js' +import { type GetPreprintTitleEnv, type PreprintTitle, getPreprintTitle } from '../../preprint.js' +import { type PageResponse, RedirectResponse, StreamlinePageResponse } from '../../response.js' import { writeReviewAddAuthorsMatch, writeReviewAuthorsMatch, writeReviewCompetingInterestsMatch, writeReviewMatch, } from '../../routes.js' +import type { IndeterminatePreprintId } from '../../types/preprint-id.js' import { type NonEmptyString, NonEmptyStringC } from '../../types/string.js' -import { type User, getUser } from '../../user.js' -import { type Form, getForm, redirectToNextForm, saveForm, updateForm } from '../form.js' +import type { User } from '../../user.js' +import { type Form, type FormStoreEnv, getForm, nextFormMatch, saveForm, updateForm } from '../form.js' import { backNav, errorPrefix, errorSummary, saveAndContinueButton } from '../shared-elements.js' -export const writeReviewCompetingInterests = flow( - RM.fromReaderTaskEitherK(getPreprintTitle), - RM.ichainW(preprint => - pipe( - RM.right({ preprint }), - RM.apS('user', getUser), - RM.apS('locale', RM.of(DefaultLocale)), - RM.bindW( - 'form', - RM.fromReaderTaskEitherK(({ user }) => getForm(user.orcid, preprint.id)), - ), - RM.apSW('method', RM.fromMiddleware(getMethod)), - RM.ichainW(state => - match(state).with({ method: 'POST' }, handleCompetingInterestsForm).otherwise(showCompetingInterestsForm), - ), - RM.orElseW(error => - match(error) - .with( - 'no-form', - 'no-session', - RM.fromMiddlewareK(() => seeOther(format(writeReviewMatch.formatter, { id: preprint.id }))), - ) - .with('form-unavailable', P.instanceOf(Error), () => serviceUnavailable) - .exhaustive(), - ), +export const writeReviewCompetingInterests = ({ + body, + id, + method, + user, +}: { + body: unknown + id: IndeterminatePreprintId + method: string + user?: User +}): RT.ReaderTask => + pipe( + getPreprintTitle(id), + RTE.matchE( + error => + RT.of( + match(error) + .with('not-found', () => pageNotFound) + .with('unavailable', () => havingProblemsPage) + .exhaustive(), + ), + preprint => + pipe( + RTE.Do, + RTE.let('preprint', () => preprint), + RTE.apS('user', pipe(RTE.fromNullable('no-session' as const)(user))), + RTE.let('locale', () => DefaultLocale), + RTE.bindW('form', ({ user }) => getForm(user.orcid, preprint.id)), + RTE.let('body', () => body), + RTE.let('method', () => method), + RTE.matchEW( + error => + RT.of( + match(error) + .with('no-form', 'no-session', () => + RedirectResponse({ location: format(writeReviewMatch.formatter, { id: preprint.id }) }), + ) + .with('form-unavailable', P.instanceOf(Error), () => havingProblemsPage) + .exhaustive(), + ), + state => + match(state) + .with({ method: 'POST' }, handleCompetingInterestsForm) + .otherwise(state => RT.of(showCompetingInterestsForm(state))), + ), + ), ), - ), - RM.orElseW(error => - match(error) - .with('not-found', () => notFound) - .with('unavailable', () => serviceUnavailable) - .exhaustive(), - ), -) - -const showCompetingInterestsForm = flow( - RM.fromReaderK( - ({ form, preprint, user, locale }: { form: Form; preprint: PreprintTitle; user: User; locale: SupportedLocale }) => - competingInterestsForm( - preprint, - { - competingInterests: E.right(form.competingInterests), - competingInterestsDetails: E.right(form.competingInterestsDetails), - }, - user, - locale, - form.moreAuthors, - ), - ), - RM.ichainFirst(() => RM.status(Status.OK)), - RM.ichainMiddlewareK(sendHtml), -) + ) -const showCompetingInterestsErrorForm = ( - preprint: PreprintTitle, - user: User, - moreAuthors: Form['moreAuthors'], - locale: SupportedLocale, -) => - flow( - RM.fromReaderK((form: CompetingInterestsForm) => competingInterestsForm(preprint, form, user, locale, moreAuthors)), - RM.ichainFirst(() => RM.status(Status.BadRequest)), - RM.ichainMiddlewareK(sendHtml), +const showCompetingInterestsForm = ({ + form, + preprint, + locale, +}: { + form: Form + preprint: PreprintTitle + locale: SupportedLocale +}) => + competingInterestsForm( + preprint, + { + competingInterests: E.right(form.competingInterests), + competingInterestsDetails: E.right(form.competingInterestsDetails), + }, + locale, + form.moreAuthors, ) +const showCompetingInterestsErrorForm = + (preprint: PreprintTitle, moreAuthors: Form['moreAuthors'], locale: SupportedLocale) => + (form: CompetingInterestsForm) => + competingInterestsForm(preprint, form, locale, moreAuthors) + const handleCompetingInterestsForm = ({ + body, form, preprint, user, locale, }: { + body: unknown form: Form preprint: PreprintTitle user: User locale: SupportedLocale }) => pipe( - RM.decodeBody(E.right), - RM.map(body => - pipe( - I.Do, - I.let('competingInterests', () => pipe(CompetingInterestsFieldD.decode(body), E.mapLeft(missingE))), - I.let('competingInterestsDetails', ({ competingInterests }) => - match(competingInterests) - .with({ right: 'yes' }, () => pipe(CompetingInterestsDetailsFieldD.decode(body), E.mapLeft(missingE))) - .otherwise(() => E.right(undefined)), - ), - ), + RTE.Do, + RTE.let('competingInterests', () => pipe(CompetingInterestsFieldD.decode(body), E.mapLeft(missingE))), + RTE.let('competingInterestsDetails', ({ competingInterests }) => + match(competingInterests) + .with({ right: 'yes' }, () => pipe(CompetingInterestsDetailsFieldD.decode(body), E.mapLeft(missingE))) + .otherwise(() => E.right(undefined)), ), - RM.chainEitherK(fields => + RTE.chainEitherK(fields => pipe( E.Do, E.apS('competingInterests', fields.competingInterests), @@ -121,14 +127,15 @@ const handleCompetingInterestsForm = ({ E.mapLeft(() => fields), ), ), - RM.map(updateForm(form)), - RM.chainFirstReaderTaskEitherKW(saveForm(user.orcid, preprint.id)), - RM.ichainMiddlewareKW(redirectToNextForm(preprint.id)), - RM.orElseW(error => - match(error) - .with('form-unavailable', () => serviceUnavailable) - .with({ competingInterests: P.any }, showCompetingInterestsErrorForm(preprint, user, form.moreAuthors, locale)) - .exhaustive(), + RTE.map(updateForm(form)), + RTE.chainFirstW(saveForm(user.orcid, preprint.id)), + RTE.matchW( + error => + match(error) + .with('form-unavailable', () => havingProblemsPage) + .with({ competingInterests: P.any }, showCompetingInterestsErrorForm(preprint, form.moreAuthors, locale)) + .exhaustive(), + form => RedirectResponse({ location: format(nextFormMatch(form).formatter, { id: preprint.id }) }), ), ) @@ -150,7 +157,6 @@ interface CompetingInterestsForm { function competingInterestsForm( preprint: PreprintTitle, form: CompetingInterestsForm, - user: User, locale: SupportedLocale, moreAuthors?: 'yes' | 'yes-private' | 'no', ) { @@ -159,146 +165,143 @@ function competingInterestsForm( const backMatch = moreAuthors === 'yes' ? writeReviewAddAuthorsMatch : writeReviewAuthorsMatch const t = translate(locale) - return templatePage({ + return StreamlinePageResponse({ + status: error ? StatusCodes.BAD_REQUEST : StatusCodes.OK, title: pipe( t('write-review', 'competingInterestsTitle')({ otherAuthors, preprintTitle: preprint.title.toString() }), errorPrefix(locale, error), plainText, ), - content: html` - + nav: backNav(locale, format(backMatch.formatter, { id: preprint.id })), + main: html` +
+ ${error ? pipe(form, toErrorItems(locale, otherAuthors), errorSummary(locale)) : ''} -
- - ${error ? pipe(form, toErrorItems(locale, otherAuthors), errorSummary(locale)) : ''} +
+ +
+ +

${t('write-review', 'doYouHaveCompetingInterests')({ otherAuthors })}

+
-
- -
- -

${t('write-review', 'doYouHaveCompetingInterests')({ otherAuthors })}

-
+

${t('write-review', 'whatIsCompetingInterest')()}

-

${t('write-review', 'whatIsCompetingInterest')()}

+
+ ${t('write-review', 'examples')()} -
- ${t('write-review', 'examples')()} +
+
    +
  • ${t('write-review', 'conflictAuthorOfPreprint')()}
  • +
  • ${t('write-review', 'conflictPersonalRelationship')()}
  • +
  • ${t('write-review', 'conflictRivalOfAuthor')()}
  • +
  • ${t('write-review', 'conflictRecentlyWorkedTogether')()}
  • +
  • ${t('write-review', 'conflictCollaborateWithAuthor')()}
  • +
  • ${t('write-review', 'conflictPublishedTogether')()}
  • +
  • ${t('write-review', 'conflictHoldGrantTogether')()}
  • +
+
+
-
-
    -
  • ${t('write-review', 'conflictAuthorOfPreprint')()}
  • -
  • ${t('write-review', 'conflictPersonalRelationship')()}
  • -
  • ${t('write-review', 'conflictRivalOfAuthor')()}
  • -
  • ${t('write-review', 'conflictRecentlyWorkedTogether')()}
  • -
  • ${t('write-review', 'conflictCollaborateWithAuthor')()}
  • -
  • ${t('write-review', 'conflictPublishedTogether')()}
  • -
  • ${t('write-review', 'conflictHoldGrantTogether')()}
  • -
-
-
- - ${E.isLeft(form.competingInterests) - ? html` -
- Error: - ${match(form.competingInterests.left) - .with({ _tag: 'MissingE' }, () => - t('write-review', 'selectYesIfCompetingInterest')({ otherAuthors }), - ) - .exhaustive()} -
- ` - : ''} + ${E.isLeft(form.competingInterests) + ? html` +
+ Error: + ${match(form.competingInterests.left) + .with({ _tag: 'MissingE' }, () => + t('write-review', 'selectYesIfCompetingInterest')({ otherAuthors }), + ) + .exhaustive()} +
+ ` + : ''} -
    -
  1. - -
  2. -
  3. - -
    -
    - +
      +
    1. + +
    2. +
    3. + +
      +
      + - ${E.isLeft(form.competingInterestsDetails) - ? html` -
      - Error: - ${match(form.competingInterestsDetails.left) - .with({ _tag: 'MissingE' }, () => - t('write-review', 'competingInterestDetails')({ otherAuthors }), - ) - .exhaustive()} -
      - ` - : ''} + ${E.isLeft(form.competingInterestsDetails) + ? html` +
      + Error: + ${match(form.competingInterestsDetails.left) + .with({ _tag: 'MissingE' }, () => + t('write-review', 'competingInterestDetails')({ otherAuthors }), + ) + .exhaustive()} +
      + ` + : ''} - -
      + .with({ right: P.select(P.string) }, identity) + .otherwise(() => '')}
      -
    4. -
    -
-
-
+
+ + + + + - ${saveAndContinueButton(locale)} - -
+ ${saveAndContinueButton(locale)} + `, - js: ['conditional-inputs.js', 'error-summary.js'], - skipLinks: [[html`Skip to form`, '#form']], - type: 'streamline', - user, + canonical: format(writeReviewCompetingInterestsMatch.formatter, { id: preprint.id }), + skipToLabel: 'form', + js: error ? ['conditional-inputs.js', 'error-summary.js'] : ['conditional-inputs.js'], }) } diff --git a/test/write-review/write-review-competing-interests.test.ts b/test/write-review/write-review-competing-interests.test.ts index ad4310212..adf7de101 100644 --- a/test/write-review/write-review-competing-interests.test.ts +++ b/test/write-review/write-review-competing-interests.test.ts @@ -1,288 +1,178 @@ import { test } from '@fast-check/jest' -import { describe, expect, jest } from '@jest/globals' +import { describe, expect } from '@jest/globals' import { format } from 'fp-ts-routing' -import * as E from 'fp-ts/lib/Either.js' import * as TE from 'fp-ts/lib/TaskEither.js' -import { MediaType, Status } from 'hyper-ts' -import * as M from 'hyper-ts/lib/Middleware.js' +import { Status } from 'hyper-ts' import Keyv from 'keyv' -import { rawHtml } from '../../src/html.js' -import type { TemplatePageEnv } from '../../src/page.js' -import { writeReviewMatch, writeReviewPublishMatch } from '../../src/routes.js' +import { writeReviewCompetingInterestsMatch, writeReviewMatch, writeReviewPublishMatch } from '../../src/routes.js' import { CompletedFormC } from '../../src/write-review/completed-form.js' import { FormC, formKey } from '../../src/write-review/form.js' import * as _ from '../../src/write-review/index.js' -import { runMiddleware } from '../middleware.js' -import { shouldNotBeCalled } from '../should-not-be-called.js' import * as fc from './fc.js' describe('writeReviewCompetingInterests', () => { test.prop([ fc.indeterminatePreprintId(), fc.preprintTitle(), - fc - .oneof( - fc.constant({ competingInterests: 'no' }), - fc.record({ competingInterests: fc.constant('yes'), competingInterestsDetails: fc.lorem() }), - ) - .chain(competingInterests => - fc.tuple( - fc.constant(competingInterests), - fc.connection({ - body: fc.constant(competingInterests), - method: fc.constant('POST'), - }), - ), - ), + fc.oneof( + fc.constant({ competingInterests: 'no' }), + fc.record({ competingInterests: fc.constant('yes'), competingInterestsDetails: fc.lorem() }), + ), fc.user(), fc.completedForm(), - ])( - 'when the form is completed', - async (preprintId, preprintTitle, [competingInterests, connection], user, newReview) => { - const formStore = new Keyv() - await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(CompletedFormC.encode(newReview))) - - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore, - getPreprintTitle: () => TE.right(preprintTitle), - getUser: () => M.of(user), - templatePage: shouldNotBeCalled, - }), - connection, - )() - - expect(await formStore.get(formKey(user.orcid, preprintTitle.id))).toMatchObject(competingInterests) - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.SeeOther }, - { - type: 'setHeader', - name: 'Location', - value: format(writeReviewPublishMatch.formatter, { id: preprintTitle.id }), - }, - { type: 'endResponse' }, - ]), - ) - }, - ) + ])('when the form is completed', async (preprintId, preprintTitle, competingInterests, user, newReview) => { + const formStore = new Keyv() + await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(CompletedFormC.encode(newReview))) + + const actual = await _.writeReviewCompetingInterests({ + body: competingInterests, + id: preprintId, + method: 'POST', + user, + })({ + formStore, + getPreprintTitle: () => TE.right(preprintTitle), + })() + + expect(await formStore.get(formKey(user.orcid, preprintTitle.id))).toMatchObject(competingInterests) + expect(actual).toStrictEqual({ + _tag: 'RedirectResponse', + status: Status.SeeOther, + location: format(writeReviewPublishMatch.formatter, { id: preprintTitle.id }), + }) + }) test.prop([ fc.indeterminatePreprintId(), fc.preprintTitle(), - fc - .oneof( - fc.constant({ competingInterests: 'no' }), - fc.record({ competingInterests: fc.constant('yes'), competingInterestsDetails: fc.lorem() }), - ) - .chain(competingInterests => - fc.tuple( - fc.constant(competingInterests), - fc.connection({ - body: fc.constant(competingInterests), - method: fc.constant('POST'), - }), - ), - ), + fc.oneof( + fc.constant({ competingInterests: 'no' }), + fc.record({ competingInterests: fc.constant('yes'), competingInterestsDetails: fc.lorem() }), + ), fc.user(), fc.incompleteForm(), - ])( - 'when the form is incomplete', - async (preprintId, preprintTitle, [competingInterests, connection], user, newReview) => { - const formStore = new Keyv() - await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(newReview)) - - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore, - getPreprintTitle: () => TE.right(preprintTitle), - getUser: () => M.of(user), - templatePage: shouldNotBeCalled, - }), - connection, - )() - - expect(await formStore.get(formKey(user.orcid, preprintTitle.id))).toMatchObject(competingInterests) - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.SeeOther }, - { - type: 'setHeader', - name: 'Location', - value: expect.stringContaining(`${format(writeReviewMatch.formatter, { id: preprintTitle.id })}/`), - }, - { type: 'endResponse' }, - ]), - ) - }, - ) - - test.prop([fc.indeterminatePreprintId(), fc.preprintTitle(), fc.connection(), fc.user()])( + ])('when the form is incomplete', async (preprintId, preprintTitle, competingInterests, user, newReview) => { + const formStore = new Keyv() + await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(newReview)) + + const actual = await _.writeReviewCompetingInterests({ + body: competingInterests, + id: preprintId, + method: 'POST', + user, + })({ + formStore, + getPreprintTitle: () => TE.right(preprintTitle), + })() + + expect(await formStore.get(formKey(user.orcid, preprintTitle.id))).toMatchObject(competingInterests) + expect(actual).toStrictEqual({ + _tag: 'RedirectResponse', + status: Status.SeeOther, + location: expect.stringContaining(`${format(writeReviewMatch.formatter, { id: preprintTitle.id })}/`), + }) + }) + + test.prop([fc.indeterminatePreprintId(), fc.preprintTitle(), fc.anything(), fc.user()])( 'when there is no form', - async (preprintId, preprintTitle, connection, user) => { - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore: new Keyv(), - getPreprintTitle: () => TE.right(preprintTitle), - getUser: () => M.of(user), - templatePage: shouldNotBeCalled, - }), - connection, - )() - - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.SeeOther }, - { - type: 'setHeader', - name: 'Location', - value: format(writeReviewMatch.formatter, { id: preprintTitle.id }), - }, - { type: 'endResponse' }, - ]), - ) + async (preprintId, preprintTitle, body, user) => { + const actual = await _.writeReviewCompetingInterests({ body, id: preprintId, method: 'POST', user })({ + formStore: new Keyv(), + getPreprintTitle: () => TE.right(preprintTitle), + })() + + expect(actual).toStrictEqual({ + _tag: 'RedirectResponse', + status: Status.SeeOther, + location: format(writeReviewMatch.formatter, { id: preprintTitle.id }), + }) }, ) - test.prop([fc.indeterminatePreprintId(), fc.connection(), fc.user(), fc.html()])( + test.prop([fc.indeterminatePreprintId(), fc.anything(), fc.string(), fc.user()])( 'when the preprint cannot be loaded', - async (preprintId, connection, user, page) => { - const templatePage = jest.fn(_ => page) - - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore: new Keyv(), - getPreprintTitle: () => TE.left('unavailable'), - getUser: () => M.of(user), - templatePage, - }), - connection, - )() - - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.ServiceUnavailable }, - { type: 'setHeader', name: 'Cache-Control', value: 'no-store, must-revalidate' }, - { type: 'setHeader', name: 'Content-Type', value: MediaType.textHTML }, - { type: 'setBody', body: page.toString() }, - ]), - ) - expect(templatePage).toHaveBeenCalledWith({ + async (preprintId, body, method, user) => { + const actual = await _.writeReviewCompetingInterests({ body, id: preprintId, method, user })({ + formStore: new Keyv(), + getPreprintTitle: () => TE.left('unavailable'), + })() + + expect(actual).toStrictEqual({ + _tag: 'PageResponse', + status: Status.ServiceUnavailable, title: expect.anything(), - content: expect.anything(), - skipLinks: [[expect.anything(), '#main-content']], - user, + main: expect.anything(), + skipToLabel: 'main', + js: [], }) }, ) - test.prop([fc.indeterminatePreprintId(), fc.connection(), fc.user(), fc.html()])( + test.prop([fc.indeterminatePreprintId(), fc.anything(), fc.string(), fc.user()])( 'when the preprint cannot be found', - async (preprintId, connection, user, page) => { - const templatePage = jest.fn(_ => page) - - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore: new Keyv(), - getPreprintTitle: () => TE.left('not-found'), - getUser: () => M.of(user), - templatePage, - }), - connection, - )() - - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.NotFound }, - { type: 'setHeader', name: 'Cache-Control', value: 'no-store, must-revalidate' }, - { type: 'setHeader', name: 'Content-Type', value: MediaType.textHTML }, - { type: 'setBody', body: page.toString() }, - ]), - ) - expect(templatePage).toHaveBeenCalledWith({ + async (preprintId, body, method, user) => { + const actual = await _.writeReviewCompetingInterests({ body, id: preprintId, method, user })({ + formStore: new Keyv(), + getPreprintTitle: () => TE.left('not-found'), + })() + + expect(actual).toStrictEqual({ + _tag: 'PageResponse', + status: Status.NotFound, title: expect.anything(), - content: expect.anything(), - skipLinks: [[expect.anything(), '#main-content']], - user, + main: expect.anything(), + skipToLabel: 'main', + js: [], }) }, ) - test.prop([fc.indeterminatePreprintId(), fc.preprintTitle(), fc.connection()])( + test.prop([fc.indeterminatePreprintId(), fc.preprintTitle(), fc.anything(), fc.string()])( "when there isn't a session", - async (preprintId, preprintTitle, connection) => { - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore: new Keyv(), - getPreprintTitle: () => TE.right(preprintTitle), - getUser: () => M.left('no-session'), - templatePage: shouldNotBeCalled, - }), - connection, - )() - - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.SeeOther }, - { - type: 'setHeader', - name: 'Location', - value: format(writeReviewMatch.formatter, { id: preprintTitle.id }), - }, - { type: 'endResponse' }, - ]), - ) + async (preprintId, preprintTitle, body, method) => { + const actual = await _.writeReviewCompetingInterests({ body, id: preprintId, method, user: undefined })({ + formStore: new Keyv(), + getPreprintTitle: () => TE.right(preprintTitle), + })() + + expect(actual).toStrictEqual({ + _tag: 'RedirectResponse', + status: Status.SeeOther, + location: format(writeReviewMatch.formatter, { id: preprintTitle.id }), + }) }, ) test.prop([ fc.indeterminatePreprintId(), fc.preprintTitle(), - fc.connection({ - body: fc.oneof( - fc.record({ competingInterests: fc.string() }, { withDeletedKeys: true }), - fc.record( - { competingInterests: fc.constant('yes'), competingInterestsDetails: fc.constant('') }, - { withDeletedKeys: true }, - ), + fc.oneof( + fc.record({ competingInterests: fc.string() }, { withDeletedKeys: true }), + fc.record( + { competingInterests: fc.constant('yes'), competingInterestsDetails: fc.constant('') }, + { withDeletedKeys: true }, ), - method: fc.constant('POST'), - }), + ), fc.user(), fc.form(), - fc.html(), - ])( - 'without declaring any competing interests', - async (preprintId, preprintTitle, connection, user, newReview, page) => { - const formStore = new Keyv() - await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(newReview)) - const templatePage = jest.fn(_ => page) - - const actual = await runMiddleware( - _.writeReviewCompetingInterests(preprintId)({ - formStore, - getPreprintTitle: () => TE.right(preprintTitle), - getUser: () => M.of(user), - templatePage, - }), - connection, - )() - - expect(actual).toStrictEqual( - E.right([ - { type: 'setStatus', status: Status.BadRequest }, - { type: 'setHeader', name: 'Content-Type', value: MediaType.textHTML }, - { type: 'setBody', body: page.toString() }, - ]), - ) - expect(templatePage).toHaveBeenCalledWith({ - title: expect.anything(), - content: expect.anything(), - skipLinks: [[rawHtml('Skip to form'), '#form']], - js: ['conditional-inputs.js', 'error-summary.js'], - type: 'streamline', - user, - }) - }, - ) + ])('without declaring any competing interests', async (preprintId, preprintTitle, body, user, newReview) => { + const formStore = new Keyv() + await formStore.set(formKey(user.orcid, preprintTitle.id), FormC.encode(newReview)) + + const actual = await _.writeReviewCompetingInterests({ body, id: preprintId, method: 'POST', user })({ + formStore, + getPreprintTitle: () => TE.right(preprintTitle), + })() + + expect(actual).toStrictEqual({ + _tag: 'StreamlinePageResponse', + canonical: format(writeReviewCompetingInterestsMatch.formatter, { id: preprintTitle.id }), + status: Status.BadRequest, + title: expect.anything(), + nav: expect.anything(), + main: expect.anything(), + skipToLabel: 'form', + js: ['conditional-inputs.js', 'error-summary.js'], + }) + }) })