Initial commit: Inventory Barcode System
This commit is contained in:
606
__tests__/ExcelExportService.test.js
Normal file
606
__tests__/ExcelExportService.test.js
Normal file
@ -0,0 +1,606 @@
|
||||
const XLSX = require('xlsx');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
// Mock dependencies before importing the service
|
||||
jest.mock('../models/database', () => ({
|
||||
getDatabase: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
promises: {
|
||||
mkdir: jest.fn(),
|
||||
writeFile: jest.fn(),
|
||||
readdir: jest.fn(),
|
||||
stat: jest.fn(),
|
||||
unlink: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
const ExcelExportService = require('../services/ExcelExportService');
|
||||
const database = require('../models/database');
|
||||
|
||||
describe('ExcelExportService', () => {
|
||||
let exportService;
|
||||
let mockDb;
|
||||
|
||||
beforeEach(() => {
|
||||
exportService = new ExcelExportService();
|
||||
|
||||
// Mock database instance
|
||||
mockDb = {
|
||||
prepare: jest.fn(),
|
||||
exec: jest.fn()
|
||||
};
|
||||
|
||||
database.getDatabase = jest.fn().mockReturnValue(mockDb);
|
||||
|
||||
// Clear all mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with correct export directory', () => {
|
||||
expect(exportService.exportDirectory).toContain(path.join('data', 'exports'));
|
||||
});
|
||||
|
||||
it('should ensure export directory exists', async () => {
|
||||
await exportService.ensureExportDirectory();
|
||||
expect(fs.mkdir).toHaveBeenCalledWith(
|
||||
expect.stringContaining(path.join('data', 'exports')),
|
||||
{ recursive: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInventoryData', () => {
|
||||
beforeEach(() => {
|
||||
const mockStmt = {
|
||||
all: jest.fn().mockReturnValue([
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Test Product 1',
|
||||
category: 'Electronics',
|
||||
unit_of_measure: 'pcs',
|
||||
current_level: 100,
|
||||
minimum_level: 10,
|
||||
maximum_level: 500,
|
||||
last_updated: '2024-01-15T10:30:00Z',
|
||||
updated_by: 'user1',
|
||||
stock_status: 'Normal'
|
||||
},
|
||||
{
|
||||
product_code: 'TEST002',
|
||||
description: 'Test Product 2',
|
||||
category: 'Tools',
|
||||
unit_of_measure: 'pcs',
|
||||
current_level: 5,
|
||||
minimum_level: 10,
|
||||
maximum_level: 100,
|
||||
last_updated: '2024-01-14T15:45:00Z',
|
||||
updated_by: 'user2',
|
||||
stock_status: 'Low Stock'
|
||||
}
|
||||
])
|
||||
};
|
||||
mockDb.prepare.mockReturnValue(mockStmt);
|
||||
});
|
||||
|
||||
it('should retrieve inventory data without filters', async () => {
|
||||
const data = await exportService.getInventoryData();
|
||||
|
||||
expect(data).toHaveLength(2);
|
||||
expect(data[0].product_code).toBe('TEST001');
|
||||
expect(data[1].stock_status).toBe('Low Stock');
|
||||
});
|
||||
|
||||
it('should apply category filter', async () => {
|
||||
await exportService.getInventoryData({ category: 'Electronics' });
|
||||
|
||||
const query = mockDb.prepare.mock.calls[0][0];
|
||||
expect(query).toContain('WHERE p.category = ?');
|
||||
});
|
||||
|
||||
it('should apply stock status filter', async () => {
|
||||
await exportService.getInventoryData({ stockStatus: 'low' });
|
||||
|
||||
const query = mockDb.prepare.mock.calls[0][0];
|
||||
expect(query).toContain('i.current_level <= i.minimum_level');
|
||||
});
|
||||
|
||||
it('should apply updated since filter', async () => {
|
||||
const since = '2024-01-01T00:00:00Z';
|
||||
await exportService.getInventoryData({ updatedSince: since });
|
||||
|
||||
const query = mockDb.prepare.mock.calls[0][0];
|
||||
expect(query).toContain('i.last_updated >= ?');
|
||||
});
|
||||
|
||||
it('should apply product codes filter', async () => {
|
||||
await exportService.getInventoryData({ productCodes: ['TEST001', 'TEST002'] });
|
||||
|
||||
const query = mockDb.prepare.mock.calls[0][0];
|
||||
expect(query).toContain('p.product_code IN (?,?)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNewExcelFile', () => {
|
||||
const mockInventoryData = [
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Test Product 1',
|
||||
category: 'Electronics',
|
||||
unit_of_measure: 'pcs',
|
||||
current_level: 100,
|
||||
minimum_level: 10,
|
||||
maximum_level: 500,
|
||||
last_updated: '2024-01-15T10:30:00Z',
|
||||
updated_by: 'user1',
|
||||
stock_status: 'Normal'
|
||||
}
|
||||
];
|
||||
|
||||
it('should create new Excel file with inventory sheet', async () => {
|
||||
const result = await exportService.createNewExcelFile(mockInventoryData);
|
||||
|
||||
expect(result.workbook).toBeDefined();
|
||||
expect(result.metadata.sheets).toContain('Inventory');
|
||||
expect(result.metadata.sheets).toContain('Summary');
|
||||
expect(result.metadata.recordCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should include history sheet when requested', async () => {
|
||||
// Mock history data
|
||||
const mockHistoryStmt = {
|
||||
all: jest.fn().mockReturnValue([
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Test Product 1',
|
||||
old_level: 90,
|
||||
new_level: 100,
|
||||
change_reason: 'Stock adjustment',
|
||||
updated_by: 'user1',
|
||||
updated_at: '2024-01-15T10:30:00Z'
|
||||
}
|
||||
])
|
||||
};
|
||||
mockDb.prepare.mockReturnValue(mockHistoryStmt);
|
||||
|
||||
const result = await exportService.createNewExcelFile(mockInventoryData, { includeHistory: true });
|
||||
|
||||
expect(result.metadata.sheets).toContain('History');
|
||||
expect(result.metadata.includeHistory).toBe(true);
|
||||
});
|
||||
|
||||
it('should exclude audit info when requested', async () => {
|
||||
const result = await exportService.createNewExcelFile(mockInventoryData, { includeAuditInfo: false });
|
||||
|
||||
expect(result.metadata.includeAuditInfo).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createInventorySheet', () => {
|
||||
const mockInventoryData = [
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Test Product 1',
|
||||
category: 'Electronics',
|
||||
unit_of_measure: 'pcs',
|
||||
current_level: 100,
|
||||
minimum_level: 10,
|
||||
maximum_level: 500,
|
||||
last_updated: '2024-01-15T10:30:00Z',
|
||||
updated_by: 'user1',
|
||||
stock_status: 'Normal'
|
||||
}
|
||||
];
|
||||
|
||||
it('should create worksheet with correct headers', () => {
|
||||
const worksheet = exportService.createInventorySheet(mockInventoryData, true);
|
||||
|
||||
// Convert worksheet to array to check content
|
||||
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
const headers = data[0];
|
||||
|
||||
expect(headers).toContain('Product Code');
|
||||
expect(headers).toContain('Description');
|
||||
expect(headers).toContain('Current Level');
|
||||
expect(headers).toContain('Last Updated');
|
||||
expect(headers).toContain('Updated By');
|
||||
});
|
||||
|
||||
it('should exclude audit info when requested', () => {
|
||||
const worksheet = exportService.createInventorySheet(mockInventoryData, false);
|
||||
|
||||
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
const headers = data[0];
|
||||
|
||||
expect(headers).not.toContain('Last Updated');
|
||||
expect(headers).not.toContain('Updated By');
|
||||
});
|
||||
|
||||
it('should include data rows with correct values', () => {
|
||||
const worksheet = exportService.createInventorySheet(mockInventoryData, true);
|
||||
|
||||
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
const dataRow = data[1];
|
||||
|
||||
expect(dataRow[0]).toBe('TEST001'); // Product Code
|
||||
expect(dataRow[1]).toBe('Test Product 1'); // Description
|
||||
expect(dataRow[4]).toBe(100); // Current Level
|
||||
});
|
||||
|
||||
it('should set column widths', () => {
|
||||
const worksheet = exportService.createInventorySheet(mockInventoryData, true);
|
||||
|
||||
expect(worksheet['!cols']).toBeDefined();
|
||||
expect(worksheet['!cols']).toHaveLength(10); // 8 base + 2 audit columns
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateOriginalExcelFile', () => {
|
||||
let originalFileBuffer;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock Excel file buffer
|
||||
const mockWorkbook = XLSX.utils.book_new();
|
||||
const mockData = [
|
||||
['Product Code', 'Description', 'Quantity'],
|
||||
['TEST001', 'Old Description', 50],
|
||||
['TEST002', 'Another Product', 25]
|
||||
];
|
||||
const mockWorksheet = XLSX.utils.aoa_to_sheet(mockData);
|
||||
XLSX.utils.book_append_sheet(mockWorkbook, mockWorksheet, 'Sheet1');
|
||||
originalFileBuffer = XLSX.write(mockWorkbook, { type: 'buffer', bookType: 'xlsx' });
|
||||
});
|
||||
|
||||
it('should update existing products in original file', async () => {
|
||||
const inventoryData = [
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Updated Description',
|
||||
current_level: 75,
|
||||
category: 'Electronics',
|
||||
last_updated: '2024-01-15T10:30:00Z'
|
||||
}
|
||||
];
|
||||
|
||||
const result = await exportService.updateOriginalExcelFile(originalFileBuffer, inventoryData);
|
||||
|
||||
expect(result.workbook).toBeDefined();
|
||||
expect(result.metadata.updatedRows).toBe(1);
|
||||
expect(result.metadata.preservedFormatting).toBe(true);
|
||||
});
|
||||
|
||||
it('should add new products when includeNewProducts is true', async () => {
|
||||
const inventoryData = [
|
||||
{
|
||||
product_code: 'TEST003',
|
||||
description: 'New Product',
|
||||
current_level: 30,
|
||||
category: 'Tools'
|
||||
}
|
||||
];
|
||||
|
||||
const result = await exportService.updateOriginalExcelFile(
|
||||
originalFileBuffer,
|
||||
inventoryData,
|
||||
{ includeNewProducts: true }
|
||||
);
|
||||
|
||||
expect(result.metadata.addedRows).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle empty original file', async () => {
|
||||
const emptyWorkbook = XLSX.utils.book_new();
|
||||
const emptyWorksheet = XLSX.utils.aoa_to_sheet([]);
|
||||
XLSX.utils.book_append_sheet(emptyWorkbook, emptyWorksheet, 'Sheet1');
|
||||
const emptyBuffer = XLSX.write(emptyWorkbook, { type: 'buffer', bookType: 'xlsx' });
|
||||
|
||||
await expect(
|
||||
exportService.updateOriginalExcelFile(emptyBuffer, [])
|
||||
).rejects.toThrow('Original Excel file appears to be empty');
|
||||
});
|
||||
|
||||
it('should handle missing sheet', async () => {
|
||||
const inventoryData = [];
|
||||
|
||||
await expect(
|
||||
exportService.updateOriginalExcelFile(originalFileBuffer, inventoryData, { sheetName: 'NonExistent' })
|
||||
).rejects.toThrow('Sheet "NonExistent" not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectColumnMappings', () => {
|
||||
it('should detect standard column mappings', () => {
|
||||
const headerRow = ['Product Code', 'Description', 'Quantity', 'Category'];
|
||||
const mapping = exportService.detectColumnMappings(headerRow);
|
||||
|
||||
expect(mapping.productCode).toBe(0);
|
||||
expect(mapping.description).toBe(1);
|
||||
expect(mapping.quantity).toBe(2);
|
||||
expect(mapping.category).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive matching', () => {
|
||||
const headerRow = ['PRODUCT_CODE', 'desc', 'QTY'];
|
||||
const mapping = exportService.detectColumnMappings(headerRow);
|
||||
|
||||
expect(mapping.productCode).toBe(0);
|
||||
expect(mapping.description).toBe(1);
|
||||
expect(mapping.quantity).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle partial matches', () => {
|
||||
const headerRow = ['Item Code', 'Product Name', 'Stock Level'];
|
||||
const mapping = exportService.detectColumnMappings(headerRow);
|
||||
|
||||
expect(mapping.productCode).toBe(0);
|
||||
expect(mapping.description).toBe(1);
|
||||
expect(mapping.quantity).toBe(2);
|
||||
});
|
||||
|
||||
it('should return null for unmatched columns', () => {
|
||||
const headerRow = ['Unknown1', 'Unknown2'];
|
||||
const mapping = exportService.detectColumnMappings(headerRow);
|
||||
|
||||
expect(mapping.productCode).toBeNull();
|
||||
expect(mapping.description).toBeNull();
|
||||
expect(mapping.quantity).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportInventoryToExcel', () => {
|
||||
beforeEach(() => {
|
||||
// Mock getInventoryData
|
||||
jest.spyOn(exportService, 'getInventoryData').mockResolvedValue([
|
||||
{
|
||||
product_code: 'TEST001',
|
||||
description: 'Test Product',
|
||||
current_level: 100,
|
||||
stock_status: 'Normal'
|
||||
}
|
||||
]);
|
||||
|
||||
// Mock createNewExcelFile
|
||||
jest.spyOn(exportService, 'createNewExcelFile').mockResolvedValue({
|
||||
workbook: XLSX.utils.book_new(),
|
||||
metadata: { recordCount: 1 }
|
||||
});
|
||||
|
||||
// Mock writeExcelFile
|
||||
jest.spyOn(exportService, 'writeExcelFile').mockResolvedValue();
|
||||
|
||||
// Mock createExportSession
|
||||
jest.spyOn(exportService, 'createExportSession').mockResolvedValue(123);
|
||||
});
|
||||
|
||||
it('should export inventory successfully', async () => {
|
||||
const result = await exportService.exportInventoryToExcel();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.filename).toMatch(/inventory_export_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.xlsx/);
|
||||
expect(result.recordCount).toBe(1);
|
||||
expect(result.sessionId).toBe(123);
|
||||
});
|
||||
|
||||
it('should use custom filename when provided', async () => {
|
||||
const customFilename = 'custom_export.xlsx';
|
||||
const result = await exportService.exportInventoryToExcel({ filename: customFilename });
|
||||
|
||||
expect(result.filename).toBe(customFilename);
|
||||
});
|
||||
|
||||
it('should handle export errors gracefully', async () => {
|
||||
jest.spyOn(exportService, 'getInventoryData').mockRejectedValue(new Error('Database error'));
|
||||
|
||||
const result = await exportService.exportInventoryToExcel();
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Database error');
|
||||
expect(result.filePath).toBeNull();
|
||||
});
|
||||
|
||||
it('should preserve original formatting when buffer provided', async () => {
|
||||
const mockBuffer = Buffer.from('mock excel data');
|
||||
jest.spyOn(exportService, 'updateOriginalExcelFile').mockResolvedValue({
|
||||
workbook: XLSX.utils.book_new(),
|
||||
metadata: { preservedFormatting: true }
|
||||
});
|
||||
|
||||
const result = await exportService.exportInventoryToExcel({
|
||||
originalFileBuffer: mockBuffer,
|
||||
preserveFormatting: true
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(exportService.updateOriginalExcelFile).toHaveBeenCalledWith(
|
||||
mockBuffer,
|
||||
expect.any(Array),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateExportFilename', () => {
|
||||
it('should generate filename with timestamp', () => {
|
||||
const filename = exportService.generateExportFilename();
|
||||
|
||||
expect(filename).toMatch(/inventory_export_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.xlsx/);
|
||||
});
|
||||
|
||||
it('should use specified format', () => {
|
||||
const filename = exportService.generateExportFilename('csv');
|
||||
|
||||
expect(filename.endsWith('.csv')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createExportSession', () => {
|
||||
beforeEach(() => {
|
||||
const mockStmt = {
|
||||
run: jest.fn().mockReturnValue({ lastInsertRowid: 456 })
|
||||
};
|
||||
mockDb.prepare.mockReturnValue(mockStmt);
|
||||
});
|
||||
|
||||
it('should create export session record', async () => {
|
||||
const sessionData = {
|
||||
filename: 'test_export.xlsx',
|
||||
totalRecords: 100,
|
||||
filters: { category: 'Electronics' },
|
||||
includeHistory: true
|
||||
};
|
||||
|
||||
const sessionId = await exportService.createExportSession(sessionData);
|
||||
|
||||
expect(sessionId).toBe(456);
|
||||
expect(mockDb.exec).toHaveBeenCalledWith(expect.stringContaining('CREATE TABLE IF NOT EXISTS export_sessions'));
|
||||
});
|
||||
|
||||
it('should handle database errors gracefully', async () => {
|
||||
mockDb.prepare.mockImplementation(() => {
|
||||
throw new Error('Database error');
|
||||
});
|
||||
|
||||
const sessionId = await exportService.createExportSession({});
|
||||
|
||||
expect(sessionId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExportHistory', () => {
|
||||
beforeEach(() => {
|
||||
const mockStmt = {
|
||||
all: jest.fn().mockReturnValue([
|
||||
{
|
||||
id: 1,
|
||||
filename: 'export1.xlsx',
|
||||
total_records: 100,
|
||||
export_date: '2024-01-15T10:30:00Z'
|
||||
}
|
||||
])
|
||||
};
|
||||
mockDb.prepare.mockReturnValue(mockStmt);
|
||||
});
|
||||
|
||||
it('should retrieve export history', async () => {
|
||||
const history = await exportService.getExportHistory();
|
||||
|
||||
expect(history).toHaveLength(1);
|
||||
expect(history[0].filename).toBe('export1.xlsx');
|
||||
});
|
||||
|
||||
it('should apply limit and offset', async () => {
|
||||
await exportService.getExportHistory({ limit: 10, offset: 5 });
|
||||
|
||||
const mockStmt = mockDb.prepare.mock.results[0].value;
|
||||
expect(mockStmt.all).toHaveBeenCalledWith(10, 5);
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
mockDb.prepare.mockImplementation(() => {
|
||||
throw new Error('Database error');
|
||||
});
|
||||
|
||||
const history = await exportService.getExportHistory();
|
||||
|
||||
expect(history).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanupOldExports', () => {
|
||||
beforeEach(() => {
|
||||
const oldDate = new Date(Date.now() - 48 * 60 * 60 * 1000); // 48 hours ago
|
||||
const newDate = new Date(Date.now() - 12 * 60 * 60 * 1000); // 12 hours ago
|
||||
|
||||
fs.readdir.mockResolvedValue(['old_export.xlsx', 'new_export.xlsx']);
|
||||
fs.stat
|
||||
.mockResolvedValueOnce({ mtime: oldDate })
|
||||
.mockResolvedValueOnce({ mtime: newDate });
|
||||
fs.unlink.mockResolvedValue();
|
||||
});
|
||||
|
||||
it('should delete old export files', async () => {
|
||||
const result = await exportService.cleanupOldExports(24);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.deletedCount).toBe(1);
|
||||
expect(fs.unlink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle cleanup errors', async () => {
|
||||
fs.readdir.mockRejectedValue(new Error('Directory error'));
|
||||
|
||||
const result = await exportService.cleanupOldExports();
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Directory error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeExcelFile', () => {
|
||||
it('should write Excel file to disk', async () => {
|
||||
const mockWorkbook = XLSX.utils.book_new();
|
||||
// Add a worksheet to make the workbook valid
|
||||
const mockWorksheet = XLSX.utils.aoa_to_sheet([['Test']]);
|
||||
XLSX.utils.book_append_sheet(mockWorkbook, mockWorksheet, 'Sheet1');
|
||||
|
||||
const filePath = '/test/path/export.xlsx';
|
||||
|
||||
await exportService.writeExcelFile(mockWorkbook, filePath, 'xlsx');
|
||||
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(filePath, expect.any(Buffer));
|
||||
});
|
||||
|
||||
it('should handle write errors', async () => {
|
||||
fs.writeFile.mockRejectedValue(new Error('Write error'));
|
||||
const mockWorkbook = XLSX.utils.book_new();
|
||||
// Add a worksheet to make the workbook valid
|
||||
const mockWorksheet = XLSX.utils.aoa_to_sheet([['Test']]);
|
||||
XLSX.utils.book_append_sheet(mockWorkbook, mockWorksheet, 'Sheet1');
|
||||
|
||||
await expect(
|
||||
exportService.writeExcelFile(mockWorkbook, '/test/path', 'xlsx')
|
||||
).rejects.toThrow('Failed to write Excel file: Write error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCellValue', () => {
|
||||
it('should return cell value as string', () => {
|
||||
const row = ['A', 'B', 'C'];
|
||||
const value = exportService.getCellValue(row, 1);
|
||||
|
||||
expect(value).toBe('B');
|
||||
});
|
||||
|
||||
it('should handle null column index', () => {
|
||||
const row = ['A', 'B', 'C'];
|
||||
const value = exportService.getCellValue(row, null);
|
||||
|
||||
expect(value).toBe('');
|
||||
});
|
||||
|
||||
it('should handle out of bounds index', () => {
|
||||
const row = ['A', 'B', 'C'];
|
||||
const value = exportService.getCellValue(row, 5);
|
||||
|
||||
expect(value).toBe('');
|
||||
});
|
||||
|
||||
it('should handle null/undefined values', () => {
|
||||
const row = ['A', null, undefined, 'D'];
|
||||
|
||||
expect(exportService.getCellValue(row, 1)).toBe('');
|
||||
expect(exportService.getCellValue(row, 2)).toBe('');
|
||||
expect(exportService.getCellValue(row, 3)).toBe('D');
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
const row = [' A ', ' B '];
|
||||
|
||||
expect(exportService.getCellValue(row, 0)).toBe('A');
|
||||
expect(exportService.getCellValue(row, 1)).toBe('B');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user