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
423 changes: 243 additions & 180 deletions document-service/doc-api/poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion document-service/doc-api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ readme = "README.md"
packages = [{include = "doc_api", from = "src"}]

[tool.poetry.dependencies]
python = ">=3.12,<3.13"
python = ">=3.12,<3.15"
flask = "^3.0.3"
flask-sqlalchemy = "^3.1.1"
Flask-Cors = "^4.0.1"
flask-migrate = "^4.0.7"
Flask-Pydantic = "^0.12.0"
pydantic = ">=2.12.0"
greenlet = ">=3.1.0"
python-dotenv = "^1.0.1"
gunicorn = "^22.0.0"
pycountry = "^24.6.1"
Expand Down
2 changes: 1 addition & 1 deletion document-service/documents-common/layers/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"autoprefixer": "^10.4.21",
"eslint": "^9.32.0",
"eslint-plugin-tailwindcss": "^3.17.5",
"nuxt": "^3.16.0",
"nuxt": "^4.4.0",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
Expand Down
4 changes: 3 additions & 1 deletion document-service/documents-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
"devDependencies": {
"@nuxt/test-utils": "^3.13.1",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"eslint": "^8.56.0",
"@nuxtjs/tailwindcss": "^6.12.0",
"@vue/test-utils": "^2.4.6",
"happy-dom": "^14.12.0",
"vitest": "^1.6.0",
"@vitest/coverage-v8": "^1.6.1",
"vitest": "^1.6.1",
"vitest-environment-nuxt": "^1.0.0"
}
}
656 changes: 396 additions & 260 deletions document-service/documents-ui/pnpm-lock.yaml

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions document-service/documents-ui/tests/unit/utils/commonUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { describe, it, expect, vi } from 'vitest'
import { scrollToTop, insensitiveStrCompare, deepChangesComparison } from '~/utils/commonUtils'

describe('commonUtils', () => {
describe('scrollToTop', () => {
it('calls window.scrollTo with top: 0 and smooth behavior', () => {
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {})
scrollToTop()
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' })
scrollToSpy.mockRestore()
})
})

describe('insensitiveStrCompare', () => {
it('returns true for identical strings', () => {
expect(insensitiveStrCompare('hello', 'hello')).toBe(true)
})

it('returns true for strings differing only in case', () => {
expect(insensitiveStrCompare('Hello', 'hello')).toBe(true)
expect(insensitiveStrCompare('WORLD', 'world')).toBe(true)
})

it('returns false for different strings', () => {
expect(insensitiveStrCompare('hello', 'world')).toBe(false)
})

it('returns true when first argument is null/undefined (optional chaining returns undefined, !undefined is true)', () => {
expect(insensitiveStrCompare(null, 'hello')).toBe(true)
expect(insensitiveStrCompare(undefined, 'hello')).toBe(true)
})

it('returns true for two empty strings', () => {
expect(insensitiveStrCompare('', '')).toBe(true)
})
})

describe('deepChangesComparison', () => {
it('returns false when two identical objects are compared', () => {
expect(deepChangesComparison({ a: 1 }, { a: 1 })).toBe(false)
})

it('returns true when objects differ', () => {
expect(deepChangesComparison({ a: 1 }, { a: 2 })).toBe(true)
})

it('returns false for case-insensitive string differences by default', () => {
expect(deepChangesComparison({ name: 'Alice' }, { name: 'alice' })).toBe(false)
})

it('returns true for case-sensitive string differences when isCaseSensitive is true', () => {
expect(deepChangesComparison({ name: 'Alice' }, { name: 'alice' }, true)).toBe(true)
})

it('returns true when boolean current differs from base', () => {
expect(deepChangesComparison(false as any, true as any)).toBe(true)
})

it('returns false when boolean current equals base', () => {
expect(deepChangesComparison(true as any, true as any)).toBe(false)
})

it('ignores null/empty properties when cleanEmptyProperties is true', () => {
const base = { a: 1, b: null }
const current = { a: 1 }
expect(deepChangesComparison(base, current)).toBe(false)
})

it('ignores undefined properties when cleanEmptyProperties is true', () => {
const base = { a: 1, b: undefined }
const current = { a: 1 }
expect(deepChangesComparison(base, current)).toBe(false)
})

it('ignores empty string properties when cleanEmptyProperties is true', () => {
const base = { a: 1, b: '' }
const current = { a: 1 }
expect(deepChangesComparison(base, current)).toBe(false)
})

it('returns false when both base and current are falsy', () => {
expect(deepChangesComparison(null as any, null as any)).toBe(false)
})

it('detects differences in nested objects', () => {
expect(deepChangesComparison({ a: { b: 1 } }, { a: { b: 2 } })).toBe(true)
})

it('returns false for two identical strings', () => {
expect(deepChangesComparison('hello' as any, 'hello' as any)).toBe(false)
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { copyToClipboard } from '~/utils/copyToClipboard'

describe('copyToClipboard', () => {
let originalClipboard: Clipboard

beforeEach(() => {
originalClipboard = navigator.clipboard
})

afterEach(() => {
Object.defineProperty(navigator, 'clipboard', {
value: originalClipboard,
writable: true,
configurable: true
})
})

it('calls navigator.clipboard.writeText with the provided text', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
writable: true,
configurable: true
})

copyToClipboard('hello world')
expect(writeText).toHaveBeenCalledWith('hello world')
})

it('logs an error when clipboard API is not supported', () => {
Object.defineProperty(navigator, 'clipboard', {
value: undefined,
writable: true,
configurable: true
})

const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
copyToClipboard('test')
expect(consoleSpy).toHaveBeenCalledWith('Clipboard API not supported')
consoleSpy.mockRestore()
})
})
106 changes: 106 additions & 0 deletions document-service/documents-ui/tests/unit/utils/dateHelper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, it, expect } from 'vitest'
import {
formatDateToISO,
formatToReadableDate,
formatIsoToYYYYMMDD,
calculatePreviousDate
} from '~/utils/dateHelper'

describe('dateHelper', () => {
describe('formatDateToISO', () => {
it('returns undefined for an empty string', () => {
expect(formatDateToISO('')).toBeUndefined()
})

it('returns a string with a timezone offset replacing Z', () => {
const result = formatDateToISO('2024-08-15')
expect(typeof result).toBe('string')
expect(result).not.toMatch(/Z$/)
expect(result).toMatch(/[+-]\d{2}:\d{2}$/)
})

it('returns a valid date portion in the result', () => {
const result = formatDateToISO('2024-06-01')
expect(result).toBeDefined()
expect(result.length).toBeGreaterThan(10)
})
})

describe('formatToReadableDate', () => {
it('returns a falsy value for an empty string', () => {
expect(formatToReadableDate('')).toBeFalsy()
})

it('includes the year in the formatted output', () => {
const result = formatToReadableDate('2024-08-13T17:21:29+00:00')
expect(result).toContain('2024')
})

it('includes Pacific time when time is not omitted', () => {
const result = formatToReadableDate('2024-08-13T17:21:29+00:00')
expect(result).toContain('Pacific time')
})

it('omits time info when omitTime is true', () => {
const result = formatToReadableDate('2024-08-13T17:21:29+00:00', true)
expect(result).not.toContain('Pacific time')
expect(result).toContain('2024')
})

it('returns a non-empty string for a valid ISO date', () => {
const result = formatToReadableDate('2024-01-01T00:00:00+00:00')
expect(result.length).toBeGreaterThan(0)
})
})

describe('formatIsoToYYYYMMDD', () => {
it('returns a string in YYYY-MM-DD format', () => {
const result = formatIsoToYYYYMMDD('2024-08-15T12:00:00.000Z')
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
})

it('returns a string in YYYY-MM-DD format for another date', () => {
const result = formatIsoToYYYYMMDD('2023-03-20T12:00:00.000Z')
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/)
})
})

describe('calculatePreviousDate', () => {
it('returns a Date earlier than now when subtracting days', () => {
const now = new Date()
const result = calculatePreviousDate('d-7')
expect(result).toBeInstanceOf(Date)
expect(result < now).toBe(true)
})

it('subtracts approximately the right number of days', () => {
const before = new Date()
const result = calculatePreviousDate('d-7')
const after = new Date()
const midNow = (before.getTime() + after.getTime()) / 2
const diffDays = Math.round((midNow - result.getTime()) / (1000 * 60 * 60 * 24))
expect(diffDays).toBe(7)
})

it('subtracts months correctly', () => {
const now = new Date()
const result = calculatePreviousDate('m-3')
expect(result).toBeInstanceOf(Date)
expect(result < now).toBe(true)
})

it('subtracts years correctly', () => {
const now = new Date()
const result = calculatePreviousDate('y-1')
expect(result.getFullYear()).toBe(now.getFullYear() - 1)
})

it('throws for an invalid format string', () => {
expect(() => calculatePreviousDate('invalid')).toThrow('Invalid input format')
})

it('throws for a numeric-only string', () => {
expect(() => calculatePreviousDate('7')).toThrow('Invalid input format')
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, it, expect, vi } from 'vitest'
import { truncate, pageSize, documentPreview } from '~/utils/documentRecords'

describe('documentRecords', () => {
describe('pageSize', () => {
it('is 100', () => {
expect(pageSize).toBe(100)
})
})

describe('documentPreview', () => {
it('calls URL.createObjectURL and returns the result', () => {
const mockUrl = 'blob:mock-url'
const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL').mockReturnValue(mockUrl)
const mockFile = new File(['content'], 'test.pdf', { type: 'application/pdf' })

const result = documentPreview(mockFile)
expect(createObjectURLSpy).toHaveBeenCalledWith(mockFile)
expect(result).toBe(mockUrl)
createObjectURLSpy.mockRestore()
})
})

describe('truncate', () => {
it('returns the original string when it does not exceed maxLength', () => {
expect(truncate('hello', 10, 5)).toBe('hello')
})

it('truncates from the end when backChars is not provided', () => {
const result = truncate('hello world', 8, 5)
expect(result).toBe('hello...')
})

it('truncates from the middle when backChars is provided', () => {
const result = truncate('hello world foo', 10, 3, 3)
expect(result).toBe('hel...foo')
})

it('returns original string when length equals maxLength', () => {
expect(truncate('abcde', 5, 3)).toBe('abcde')
})

it('keeps front characters correctly in end truncation', () => {
const result = truncate('abcdefghij', 6, 4)
expect(result).toBe('abcd...')
})

it('keeps back characters correctly in middle truncation', () => {
const result = truncate('abcdefghij', 6, 2, 2)
expect(result).toBe('ab...ij')
})

it('handles long strings with end truncation', () => {
const long = 'a'.repeat(100)
const result = truncate(long, 10, 7)
expect(result).toBe('aaaaaaa...')
expect(result.length).toBe(10)
})
})
})
Loading
Loading