From cdb9587ac9e9ebdbe5d4e023f27c2129aeeaf5bd Mon Sep 17 00:00:00 2001 From: jekabskarklins Date: Tue, 30 Jun 2026 10:01:51 +0200 Subject: [PATCH 1/3] fix: interpret mjs as text/javascript --- apps/frontend/Dockerfile | 2 +- apps/frontend/default.conf | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile index 1490a35e44..351f043627 100644 --- a/apps/frontend/Dockerfile +++ b/apps/frontend/Dockerfile @@ -23,7 +23,7 @@ COPY apps/frontend/ /app/frontend RUN npm run build # Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx -FROM nginxinc/nginx-unprivileged:1.25-alpine +FROM nginxinc/nginx-unprivileged:alpine3.23 COPY --from=build-stage /app/frontend/build/ /usr/share/nginx/html COPY --from=build-stage /app/frontend/build-version.txt /usr/share/nginx/html diff --git a/apps/frontend/default.conf b/apps/frontend/default.conf index d193267366..fefe0925cf 100644 --- a/apps/frontend/default.conf +++ b/apps/frontend/default.conf @@ -1,6 +1,7 @@ server { listen 8080; - + root /usr/share/nginx/html; + gzip on; gzip_disable "msie6"; @@ -10,9 +11,17 @@ server { gzip_http_version 1.1; gzip_types text/plain application/javascript text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype; + # nginx's bundled mime.types has no mapping for .mjs, so ES module workers + # (e.g. pdf.worker.min.mjs) would be served as application/octet-stream and + # rejected by the browser's strict module MIME check. Set the type for .mjs + # only, without redefining the whole types map (a `types { }` block would + # REPLACE the inherited mime.types and break .html/.css serving). + location ~ \.mjs$ { + default_type text/javascript; + } + location / { - root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =200; } -} \ No newline at end of file +} From 7d7bfa26e42582c7f19ef9b19b41d3e44fcc6486 Mon Sep 17 00:00:00 2001 From: jekabskarklins Date: Tue, 30 Jun 2026 10:36:06 +0200 Subject: [PATCH 2/3] fix: add test --- .../cypress/e2e/proposalPdfTemplates.cy.ts | 20 +++++++++++++++++++ .../pdf/proposal/PdfTemplateEditorViewer.tsx | 11 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts b/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts index aa2367c4da..1c6b2832d1 100644 --- a/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts +++ b/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts @@ -218,6 +218,26 @@ context('Proposal PDF template tests', () => { cy.contains(newDescription).should('exist'); }); + it('User officer opens a PDF template editor → PDF preview renders without error', () => { + cy.login('officer'); + cy.visit('/'); + + cy.navigateToTemplatesSubmenu('PDF (Proposal)'); + + cy.contains(createdTemplateName) + .parent() + .find("[aria-label='Edit']") + .click(); + + cy.contains(createdTemplateName).should('exist'); + + cy.get('[data-cy="pdf-template-preview"] canvas', { + timeout: 60000, + }).should('be.visible'); + + cy.get('[data-cy="pdf-template-preview-error"]').should('not.exist'); + }); + it('User officer can clone PDF template', () => { cy.login('officer'); cy.visit('/'); diff --git a/apps/frontend/src/components/template/pdf/proposal/PdfTemplateEditorViewer.tsx b/apps/frontend/src/components/template/pdf/proposal/PdfTemplateEditorViewer.tsx index b1e6a7d06c..879c9cc878 100644 --- a/apps/frontend/src/components/template/pdf/proposal/PdfTemplateEditorViewer.tsx +++ b/apps/frontend/src/components/template/pdf/proposal/PdfTemplateEditorViewer.tsx @@ -42,8 +42,17 @@ const PDFViewer = ({ fileUrl }: { fileUrl: string }) => { height: '1000px', }} ref={setContainerRef} + data-cy="pdf-template-preview" > - + + Failed to load PDF file. + + } + > {Array.from(new Array(numPages), (_el, index) => ( Date: Thu, 2 Jul 2026 17:54:47 +0200 Subject: [PATCH 3/3] fix: fix the test mock data so that the template can render in factorh --- .../cypress/e2e/proposalPdfTemplates.cy.ts | 534 +++++++++++++++++- apps/e2e/cypress/support/template.ts | 13 + apps/e2e/cypress/types/template.d.ts | 15 + 3 files changed, 561 insertions(+), 1 deletion(-) diff --git a/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts b/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts index 1c6b2832d1..4a9884bfd6 100644 --- a/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts +++ b/apps/e2e/cypress/e2e/proposalPdfTemplates.cy.ts @@ -98,6 +98,538 @@ context('Proposal PDF template tests', () => { createdTemplateName = result.createTemplate.name; } }); + + cy.updateProposalPdfTemplate({ + proposalPdfTemplateId: 1, + dummyData: `{ + "data": { + "proposal": { + "primaryKey": 4, + "title": "New Proposal", + "abstract": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum", + "proposerId": 1, + "statusId": 1, + "created": "2023-10-18T11:38:15.429Z", + "updated": "2023-10-18T11:39:02.691Z", + "proposalId": "268490", + "callId": 1, + "questionaryId": 4, + "notified": false, + "submitted": true, + "referenceNumberSequence": 3, + "managementTimeAllocation": null, + "managementDecisionSubmitted": false + }, + "principalInvestigator": { + "id": 1, + "firstname": "Carl", + "lastname": "Carlsson", + "preferredname": "Carl", + "institution": "Other", + "institutionId": 1, + "position": "Strategist", + "created": "2023-10-10T13:32:18.172Z", + "placeholder": false, + "email": "Javon4@hotmail.com" + }, + "coProposers": [ + { + "id": 4, + "firstname": "Benjamin", + "lastname": "Beckley", + "preferredname": "Benjamin", + "institution": "Other", + "institutionId": 1, + "position": "Management", + "created": "2023-10-18T11:38:15.429Z", + "placeholder": false, + "email": "ben@inbox.com" + } + ], + "questionarySteps": [ + { + "questionaryId": 4, + "topic": { + "id": 41, + "title": "Samples & Info", + "templateId": 39, + "sortOrder": 1, + "isEnabled": true + }, + "isCompleted": true, + "fields": [ + { + "question": { + "categoryId": 1, + "id": "date_1661426719613", + "naturalKey": "date_1661426719613", + "dataType": "DATE", + "question": "When do you want to come?", + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "includeTime": false, + "maxDate": null, + "minDate": null, + "defaultDate": null + } + }, + "topicId": 41, + "sortOrder": 0, + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "includeTime": false, + "maxDate": null, + "minDate": null, + "defaultDate": null + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 4, + "value": null + }, + { + "question": { + "categoryId": 1, + "id": "sample_declaration_1666269436134", + "naturalKey": "sample_declaration_1666269436134", + "dataType": "SAMPLE_DECLARATION", + "question": "Samples", + "config": { + "addEntryButtonLabel": "Add", + "templateCategory": "SAMPLE_DECLARATION", + "templateId": 36, + "esiTemplateId": null, + "small_label": "", + "required": false, + "maxEntries": null, + "minEntries": null + } + }, + "topicId": 41, + "sortOrder": 1, + "config": { + "addEntryButtonLabel": "Add", + "templateCategory": "SAMPLE_DECLARATION", + "templateId": 36, + "esiTemplateId": null, + "small_label": "", + "required": false, + "maxEntries": null, + "minEntries": null + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": null, + "value": [] + }, + { + "question": { + "categoryId": 1, + "id": "selection_from_options_1605784613207", + "naturalKey": "selection_from_options_instrument", + "dataType": "SELECTION_FROM_OPTIONS", + "question": "Select an instrument", + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "variant": "dropdown", + "options": ["LoKi", "MX"], + "isMultipleSelect": false + } + }, + "topicId": 41, + "sortOrder": 2, + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "variant": "dropdown", + "options": ["LoKi", "MX", "CRYO-EM"], + "isMultipleSelect": true + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 5, + "value": ["MX"] + }, + { + "question": { + "categoryId": 1, + "id": "generic_template_1674814357714", + "naturalKey": "generic_template_1674814357714", + "dataType": "GENERIC_TEMPLATE", + "question": "MX", + "config": { + "addEntryButtonLabel": "Add", + "copyButtonLabel": "", + "canCopy": false, + "isMultipleCopySelect": false, + "isCompleteOnCopy": false, + "templateCategory": "GENERIC_TEMPLATE", + "templateId": 38, + "small_label": "", + "required": false, + "maxEntries": "1", + "minEntries": "1" + } + }, + "topicId": 41, + "sortOrder": 5, + "config": { + "addEntryButtonLabel": "Add", + "copyButtonLabel": "", + "canCopy": false, + "isMultipleCopySelect": false, + "isCompleteOnCopy": false, + "templateCategory": "GENERIC_TEMPLATE", + "templateId": 38, + "small_label": "", + "required": false, + "maxEntries": 1, + "minEntries": 1 + }, + "dependencies": [ + { + "questionId": "generic_template_1674814357714", + "dependencyId": "selection_from_options_1605784613207", + "dependencyNaturalKey": "selection_from_options_instrument", + "condition": { + "params": "MX", + "condition": "eq" + } + } + ], + "dependenciesOperator": "AND", + "answerId": null, + "value": [ + { + "id": 1, + "title": "template 1", + "creatorId": 1, + "proposalPk": 4, + "questionaryId": 5, + "questionId": "generic_template_1674814357714", + "created": "2023-10-18T11:38:39.861Z" + } + ] + }, + { + "question": { + "categoryId": 1, + "id": "boolean_1674812264143", + "naturalKey": "boolean_1674812264143", + "dataType": "BOOLEAN", + "question": "Do you want sample environment for MX", + "config": { + "small_label": "", + "required": false, + "tooltip": "" + } + }, + "topicId": 41, + "sortOrder": 6, + "config": { + "small_label": "", + "required": false, + "tooltip": "" + }, + "dependencies": [ + { + "questionId": "boolean_1674812264143", + "dependencyId": "selection_from_options_1605784613207", + "dependencyNaturalKey": "selection_from_options_instrument", + "condition": { + "params": "MX", + "condition": "eq" + } + } + ], + "dependenciesOperator": "AND", + "answerId": 6, + "value": true + }, + { + "question": { + "categoryId": 1, + "id": "embellishment_1636710963252", + "naturalKey": "embellishment_1636710963252", + "dataType": "EMBELLISHMENT", + "question": "New question", + "config": { + "html": "

Users can also submit Expression of Interest in the following Crystallisation services:

\\n
    \\n
  • Support for large protein crystal growth of either protiated or deuterated proteins (incl. sitting drop vapour diffusion, dialysis, batch, optimization, targeted screening, testing with X-rays)
  • \\n
", + "plain": "Crystallisation text", + "omitFromPdf": true + } + }, + "topicId": 41, + "sortOrder": 7, + "config": { + "html": "

Users can also submit Expression of Interest in the following Crystallisation services:

\\n
    \\n
  • Support for large protein crystal growth of either protiated or deuterated proteins (incl. sitting drop vapour diffusion, dialysis, batch, optimization, targeted screening, testing with X-rays)
  • \\n
", + "plain": "Crystallisation text", + "omitFromPdf": true + }, + "dependencies": [ + { + "questionId": "embellishment_1636710963252", + "dependencyId": "selection_from_options_1605784613207", + "dependencyNaturalKey": "selection_from_options_instrument", + "condition": { + "params": "MX", + "condition": "eq" + } + } + ], + "dependenciesOperator": "AND", + "answerId": null, + "value": null + } + ] + }, + { + "questionaryId": 4, + "topic": { + "id": 42, + "title": "Sample environment", + "templateId": 39, + "sortOrder": 2, + "isEnabled": true + }, + "isCompleted": true, + "fields": [ + { + "question": { + "categoryId": 1, + "id": "boolean_1674821942563", + "naturalKey": "boolean_1674821942563", + "dataType": "BOOLEAN", + "question": "Do you need lab-support?", + "config": { + "small_label": "", + "required": false, + "tooltip": "" + } + }, + "topicId": 42, + "sortOrder": 0, + "config": { + "small_label": "", + "required": false, + "tooltip": "" + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 7, + "value": true + }, + { + "question": { + "categoryId": 1, + "id": "final_delivery_date_motivation", + "naturalKey": "final_delivery_date_motivation", + "dataType": "TEXT_INPUT", + "question": "Please give a brief explanation for the chosen date", + "config": { + "required": true, + "small_label": "", + "tooltip": "", + "htmlQuestion": "", + "isHtmlQuestion": false, + "min": "1", + "max": 500, + "multiline": true, + "placeholder": "(maximum 500 characters)", + "isCounterHidden": false + } + }, + "topicId": 42, + "sortOrder": 1, + "config": { + "required": true, + "small_label": "", + "tooltip": "", + "htmlQuestion": "", + "isHtmlQuestion": false, + "min": 1, + "max": 500, + "multiline": true, + "placeholder": "(maximum 500 characters)", + "isCounterHidden": false + }, + "dependencies": [ + { + "questionId": "final_delivery_date_motivation", + "dependencyId": "boolean_1674821942563", + "dependencyNaturalKey": "boolean_1674821942563", + "condition": { + "params": true, + "condition": "eq" + } + } + ], + "dependenciesOperator": "AND", + "answerId": 8, + "value": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, an" + } + ] + } + ], + "attachments": [], + "samples": [], + "genericTemplates": [ + { + "genericTemplate": { + "id": 1, + "title": "template 1", + "creatorId": 1, + "proposalPk": 4, + "questionaryId": 5, + "questionId": "generic_template_1674814357714", + "created": "2023-10-18T11:38:39.861Z" + }, + "genericTemplateQuestionaryFields": [ + { + "question": { + "categoryId": 7, + "id": "generic_template_basis", + "naturalKey": "generic_template_basis", + "dataType": "GENERIC_TEMPLATE_BASIS", + "question": "Template basic information", + "config": { + "titlePlaceholder": "Title", + "questionLabel": "", + "tooltip": "", + "required": false, + "small_label": "" + } + }, + "topicId": 39, + "sortOrder": 0, + "config": { + "titlePlaceholder": "Title", + "questionLabel": "", + "tooltip": "", + "required": false, + "small_label": "" + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": null, + "value": null + }, + { + "question": { + "categoryId": 7, + "id": "boolean_1674814267658", + "naturalKey": "boolean_1674814267658", + "dataType": "BOOLEAN", + "question": "New question", + "config": { + "small_label": "", + "required": false, + "tooltip": "" + } + }, + "topicId": 39, + "sortOrder": 1, + "config": { + "small_label": "", + "required": false, + "tooltip": "" + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 1, + "value": true + }, + { + "question": { + "categoryId": 7, + "id": "date_1674814304449", + "naturalKey": "date_1674814304449", + "dataType": "DATE", + "question": "New question", + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "includeTime": false, + "maxDate": null, + "minDate": null, + "defaultDate": null + } + }, + "topicId": 39, + "sortOrder": 2, + "config": { + "small_label": "", + "required": false, + "tooltip": "", + "includeTime": false, + "maxDate": null, + "minDate": null, + "defaultDate": null + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 2, + "value": "2023-10-19T22:00:00.000Z" + }, + { + "question": { + "categoryId": 7, + "id": "text_input_1674814274952", + "naturalKey": "text_input_1674814274952", + "dataType": "TEXT_INPUT", + "question": "New question", + "config": { + "required": false, + "small_label": "", + "tooltip": "", + "htmlQuestion": "", + "isHtmlQuestion": false, + "min": null, + "max": null, + "multiline": false, + "placeholder": "", + "isCounterHidden": false + } + }, + "topicId": 39, + "sortOrder": 3, + "config": { + "required": false, + "small_label": "", + "tooltip": "", + "htmlQuestion": "", + "isHtmlQuestion": false, + "min": null, + "max": null, + "multiline": false, + "placeholder": "", + "isCounterHidden": false + }, + "dependencies": [], + "dependenciesOperator": "AND", + "answerId": 3, + "value": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum" + } + ] + } + ] + }, + "userRole": { + "id": 2, + "shortCode": "user_officer", + "title": "User Officer" + } +} +`, + }); }); it('User officer can modify PDF template', () => { @@ -218,7 +750,7 @@ context('Proposal PDF template tests', () => { cy.contains(newDescription).should('exist'); }); - it('User officer opens a PDF template editor → PDF preview renders without error', () => { + it.only('User officer opens a PDF template editor → PDF preview renders without error', () => { cy.login('officer'); cy.visit('/'); diff --git a/apps/e2e/cypress/support/template.ts b/apps/e2e/cypress/support/template.ts index b36fe60fc0..324e909bc0 100644 --- a/apps/e2e/cypress/support/template.ts +++ b/apps/e2e/cypress/support/template.ts @@ -20,6 +20,8 @@ import { UpdateQuestionMutationVariables, UpdateQuestionTemplateRelationSettingsMutation, UpdateQuestionTemplateRelationSettingsMutationVariables, + UpdateProposalPdfTemplateMutation, + UpdateProposalPdfTemplateMutationVariables, UpdateTopicMutation, UpdateTopicMutationVariables, } from '@user-office-software-libs/shared-types'; @@ -73,6 +75,15 @@ const setActiveTemplate = ( return cy.wrap(request); }; +const updateProposalPdfTemplate = ( + updateProposalPdfTemplateInput: UpdateProposalPdfTemplateMutationVariables +): Cypress.Chainable => { + const api = getE2EApi(); + const request = api.updateProposalPdfTemplate(updateProposalPdfTemplateInput); + + return cy.wrap(request); +}; + const typeToMenuTitle = new Map(); typeToMenuTitle.set('proposal', 'Proposal'); typeToMenuTitle.set('sample', 'Sample declaration'); @@ -810,3 +821,5 @@ Cypress.Commands.add( ); Cypress.Commands.add('setActiveTemplate', setActiveTemplate); + +Cypress.Commands.add('updateProposalPdfTemplate', updateProposalPdfTemplate); diff --git a/apps/e2e/cypress/types/template.d.ts b/apps/e2e/cypress/types/template.d.ts index 5e16ceb86a..e1b461ec46 100644 --- a/apps/e2e/cypress/types/template.d.ts +++ b/apps/e2e/cypress/types/template.d.ts @@ -21,6 +21,8 @@ import { UpdateQuestionTemplateRelationSettingsMutationVariables, UpdateQuestionTemplateRelationSettingsMutation, SetActiveTemplateMutationVariables, + UpdateProposalPdfTemplateMutationVariables, + UpdateProposalPdfTemplateMutation, } from '@user-office-software-libs/shared-types'; declare global { @@ -315,6 +317,7 @@ declare global { createTemplate: ( createTemplateInput: CreateTemplateMutationVariables ) => Cypress.Chainable; + /** * Clone template * @@ -397,6 +400,18 @@ declare global { setActiveTemplate: ( setActiveTemplateInput: SetActiveTemplateMutationVariables ) => Cypress.Chainable; + + /** + * Updates proposal PDF template + * + * @returns {typeof updateProposalPdfTemplate} + * @memberof Chainable + * @example + * cy.updateProposalPdfTemplate(updateProposalPdfTemplateInput: UpdateProposalPdfTemplateMutationVariables) + */ + updateProposalPdfTemplate: ( + updateProposalPdfTemplateInput: UpdateProposalPdfTemplateMutationVariables + ) => Cypress.Chainable; } } }