Skip to content
Draft
410 changes: 410 additions & 0 deletions PR-174-TEST-REPORT.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export default {
// }
// },
// },

validation: {
handler(newValidation, oldValidation) {
if (this.isLoaded) {
Expand Down Expand Up @@ -196,7 +197,7 @@ export default {
var configs = this.tableJSON.schema.fields.map((column: DataFrameField) => {
const commonConfig = this.generateColumnConfig(column.name);
const editableConfig = this.generateColumnEditableConfig(column.name);
return { ...commonConfig, ...editableConfig };
return { ...commonConfig, ...editableConfig, ...column };
});

if (!this.editable) {
Expand Down Expand Up @@ -913,6 +914,7 @@ export default {

this.tabulator.on("cellEdited", (cell: CellComponent) => {
this.updateTableJsonData();
this.$emit("cell-edited", cell);
// const rowPos: number | boolean = cell.getRow().getPosition();
// if (typeof rowPos != 'number' || rowPos < 0 || rowPos > this.tableJSON.data.length) return;
// this.$set(this.tableJSON.data[rowPos-1], cell.getColumn().getField(), cell.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:editable="editable"
:hasValidValues="hasValidValues"
:questions="questions"
:validation="validation || validators"
@table-built="$emit('table-built')"
@row-click="(e, row) => $emit('row-click', e, row)"
@cell-edited="(cell) => $emit('cell-edited', cell)"
Expand Down Expand Up @@ -71,9 +72,11 @@ export default {
computed: {
// Convert simple data/columns to TableData format for RenderTable
computedTableJSON(): TableData {
// FIX 1: Use "...col" to preserve editor config (dropdowns), validators, and freezing
const fields = this.columns.map((col: any) => ({
name: col.field,
type: col.type || "string",
...col // <--- THIS IS THE MAGIC FIX
}));

const schema = new DataFrameSchema(
Expand All @@ -89,7 +92,14 @@ export default {
null
);

if (this.validation) {
// FIX 2: Handle both 'validation' and 'validators' props
// ImportFileUpload passes :validators, so we must prioritize that.
if (this.validators) {
// We wrap it in a structure RenderTable understands
tableData.validation = { columns: {}, ...this.validation };
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The validator handling logic is incorrect. When validators prop is provided, the code assigns { columns: {}, ...this.validation } to tableData.validation, which spreads the wrong object. It should use this.validators instead of this.validation. This will cause validators passed from ImportFileUpload to not work properly.

Suggested change
tableData.validation = { columns: {}, ...this.validation };
tableData.validation = { columns: {}, ...this.validators };

Copilot uses AI. Check for mistakes.
// We might need to pass this directly to the RenderTable prop instead,
// but attaching it to tableData is the safest way for the Schema to know about it.
} else if (this.validation) {
tableData.validation = this.validation;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ describe("useDocumentViewModel", () => {

// Get the computed function that was passed to mockComputed
const computedCalls = mockComputed.mock.calls;
const hasDocumentLoadedCall = computedCalls.find(
(call) => call[0].toString().includes("document.id")
);
const hasDocumentLoadedCall = computedCalls.find((call) => call[0].toString().includes("document.id"));

expect(hasDocumentLoadedCall).toBeDefined();
const hasDocumentLoaded = hasDocumentLoadedCall[0]();
Expand All @@ -127,9 +125,7 @@ describe("useDocumentViewModel", () => {

// Get the computed function that was passed to mockComputed
const computedCalls = mockComputed.mock.calls;
const hasDocumentLoadedCall = computedCalls.find(
(call) => call[0].toString().includes("document.id")
);
const hasDocumentLoadedCall = computedCalls.find((call) => call[0].toString().includes("document.id"));

expect(hasDocumentLoadedCall).toBeDefined();
const hasDocumentLoaded = hasDocumentLoadedCall[0]();
Expand All @@ -144,7 +140,7 @@ describe("useDocumentViewModel", () => {

mockGetDocumentUseCase.createParams.mockReturnValue({
workspace_id: workspaceId,
pmid: "12345"
pmid: "12345",
});

useDocumentViewModel({ record: { metadata } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

<div
class="settings__edition-form__group --subcategories"
v-if="question.isMultiLabelType || question.isSingleLabelType || question.isSpanType || question.isRankingType"
v-if="
question.isMultiLabelType || question.isSingleLabelType || question.isSpanType || question.isRankingType
"
>
<label :for="`options-${question.id}`" v-text="$t('question.labels')" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useSettingsQuestionsViewModel = () => {

question.settings.options.push({
value: text.toLowerCase().replace(/\s+/g, '_'),
text: text
text
});

newLabelText.value[question.id] = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</BaseButton>
</div>
<BaseButton @click="addOption" class="secondary small dataset-config-ranking__add-button"
><svgicon name="plus" width="12" color="var(--fg-secondary)" /> {{$t("question.addLabel")}}</BaseButton
><svgicon name="plus" width="12" color="var(--fg-secondary)" /> {{ $t("question.addLabel") }}</BaseButton
>
</div>
</template>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import DocumentsList from './DocumentsList.vue';
import { Document } from '~/v1/domain/entities/document/Document';
import { mount } from "@vue/test-utils";
import DocumentsList from "./DocumentsList.vue";
import { Document } from "~/v1/domain/entities/document/Document";

// Mock the view model
const mockShowDocumentMetadata = jest.fn();
Expand All @@ -13,36 +13,36 @@ const mockViewModel = {
totalFiles: 0,
showMetadataModal: false,
selectedDocumentMetadata: null,
selectedDocumentName: '',
selectedDocumentName: "",
loadDocuments: jest.fn(),
openDocument: jest.fn(),
showDocumentMetadata: mockShowDocumentMetadata,
closeMetadataModal: mockCloseMetadataModal,
};

jest.mock('./useDocumentsListViewModel', () => ({
jest.mock("./useDocumentsListViewModel", () => ({
useDocumentsListViewModel: () => mockViewModel,
}));

// Mock base components
jest.mock('~/components/base/base-modal/BaseModal.vue', () => ({
name: 'BaseModal',
jest.mock("~/components/base/base-modal/BaseModal.vue", () => ({
name: "BaseModal",
template: '<div class="base-modal"><slot /></div>',
props: ['modalVisible', 'modalTitle', 'modalClass'],
props: ["modalVisible", "modalTitle", "modalClass"],
}));

jest.mock('~/components/base/base-button/BaseButton.vue', () => ({
name: 'BaseButton',
jest.mock("~/components/base/base-button/BaseButton.vue", () => ({
name: "BaseButton",
template: '<button class="base-button" @click="$emit(\'click\')"><slot /></button>',
}));

describe('DocumentsList', () => {
describe("DocumentsList", () => {
let wrapper;

const createWrapper = (props = {}) => {
return mount(DocumentsList, {
propsData: {
workspaceId: 'test-workspace',
workspaceId: "test-workspace",
...props,
},
stubs: {
Expand All @@ -67,7 +67,7 @@ describe('DocumentsList', () => {
mockViewModel.documents = [];
mockViewModel.showMetadataModal = false;
mockViewModel.selectedDocumentMetadata = null;
mockViewModel.selectedDocumentName = '';
mockViewModel.selectedDocumentName = "";
mockViewModel.groupedDocuments = [];

wrapper = createWrapper();
Expand All @@ -77,27 +77,29 @@ describe('DocumentsList', () => {
wrapper.destroy();
});

describe('metadata modal functionality', () => {
it('should show metadata button when document has metadata', async () => {
describe("metadata modal functionality", () => {
it("should show metadata button when document has metadata", async () => {
const documentWithMetadata = new Document(
'doc-1',
'http://example.com/doc.pdf',
'test.pdf',
'pmid123',
'doi123',
"doc-1",
"http://example.com/doc.pdf",
"test.pdf",
"pmid123",
"doi123",
1,
'Test Reference',
"Test Reference",
[],
{ workflow_status: 'completed', analysis_metadata: { ocr_quality: { total_chars: 1000 } } }
{ workflow_status: "completed", analysis_metadata: { ocr_quality: { total_chars: 1000 } } }
);

// Update the mock view model data instead of using setData
mockViewModel.documents = [documentWithMetadata];
mockViewModel.groupedDocuments = [{
reference: 'Test Reference',
documents: [documentWithMetadata],
metadata: documentWithMetadata.metadata
}];
mockViewModel.groupedDocuments = [
{
reference: "Test Reference",
documents: [documentWithMetadata],
metadata: documentWithMetadata.metadata,
},
];

await wrapper.vm.$nextTick();

Expand All @@ -106,33 +108,35 @@ describe('DocumentsList', () => {
expect(mockViewModel.documents[0].metadata).toBeDefined();
});

it('should open metadata modal when metadata button is clicked', async () => {
it("should open metadata modal when metadata button is clicked", async () => {
const testMetadata = {
workflow_status: 'completed',
workflow_status: "completed",
analysis_metadata: {
ocr_quality: { total_chars: 1000, ocr_quality_score: 0.95 }
}
ocr_quality: { total_chars: 1000, ocr_quality_score: 0.95 },
},
};

const documentWithMetadata = new Document(
'doc-1',
'http://example.com/doc.pdf',
'test-document.pdf',
'pmid123',
'doi123',
"doc-1",
"http://example.com/doc.pdf",
"test-document.pdf",
"pmid123",
"doi123",
1,
'Test Reference',
"Test Reference",
[],
testMetadata
);

// Update the mock view model data
mockViewModel.documents = [documentWithMetadata];
mockViewModel.groupedDocuments = [{
reference: 'Test Reference',
documents: [documentWithMetadata],
metadata: documentWithMetadata.metadata
}];
mockViewModel.groupedDocuments = [
{
reference: "Test Reference",
documents: [documentWithMetadata],
metadata: documentWithMetadata.metadata,
},
];

await wrapper.vm.$nextTick();

Expand All @@ -144,11 +148,11 @@ describe('DocumentsList', () => {
expect(mockShowDocumentMetadata).toHaveBeenCalledTimes(1);
});

it('should close metadata modal when closeMetadataModal is called', () => {
it("should close metadata modal when closeMetadataModal is called", () => {
// Set up initial modal state
mockViewModel.showMetadataModal = true;
mockViewModel.selectedDocumentMetadata = { some: 'data' };
mockViewModel.selectedDocumentName = 'test.pdf';
mockViewModel.selectedDocumentMetadata = { some: "data" };
mockViewModel.selectedDocumentName = "test.pdf";

// Call the method through the mock
mockCloseMetadataModal();
Expand All @@ -157,4 +161,4 @@ describe('DocumentsList', () => {
expect(mockCloseMetadataModal).toHaveBeenCalledTimes(1);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export const useImportAnalysisTableViewModel = (props: {
workspaceId: props.workspace?.id,
dataframeLength: props.dataframeData?.data?.length,
matchedFilesLength: props.pdfData?.matchedFiles?.length,
// Add deep watch on dataframe data to catch cell edits
dataframeDataHash: props.dataframeData ? JSON.stringify(props.dataframeData.data) : null,
}),
(newVal, oldVal) => {
// Only trigger if we have all required data and something actually changed
Expand All @@ -107,7 +109,8 @@ export const useImportAnalysisTableViewModel = (props: {
!oldVal ||
newVal.workspaceId !== oldVal.workspaceId ||
newVal.dataframeLength !== oldVal.dataframeLength ||
newVal.matchedFilesLength !== oldVal.matchedFilesLength
newVal.matchedFilesLength !== oldVal.matchedFilesLength ||
newVal.dataframeDataHash !== oldVal.dataframeDataHash
) {
analyzeImport(props.workspace!, props.dataframeData!, props.pdfData!.matchedFiles);
}
Expand Down
Loading