Skip to content
Merged
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
10 changes: 8 additions & 2 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,14 @@ jobs:
fi
bun run check:migrations "$BASE_REF"

- name: Type-check realtime server
run: bunx turbo run type-check --filter=@sim/realtime
# Every workspace, not just realtime. packages/emcn, packages/utils,
# apps/desktop and apps/docs had no type check in CI at all; apps/sim's
# source was covered only as a side effect of `next build` in the separate
# Build App job. Note this does NOT cover apps/sim's tests — its tsconfig
# excludes *.test.ts(x), and including them today surfaces ~2.2k errors,
# so that is its own cleanup rather than a gate to switch on here.
- name: Type-check all workspaces
run: bunx turbo run type-check

# cloud-review-tools.test.ts runs the real helper on the runner, which shells
# out to rg. Blacksmith's image ships it, GitHub's doesn't.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,10 @@ export function AddConnectorModal({
<>
<ChipModal
open={open}
onOpenChange={(val) => !isCreating && onOpenChange(val)}
onOpenChange={onOpenChange}
srTitle={step === 'select-type' ? 'Connect Source' : `Configure ${connectorConfig?.name}`}
size='md'
dismissDisabled={isCreating}
>
<ChipModalHeader onClose={() => onOpenChange(false)}>
{step === 'configure' ? (
Expand Down Expand Up @@ -428,7 +429,6 @@ export function AddConnectorModal({
{step === 'configure' && (
<ChipModalFooter
onCancel={() => onOpenChange(false)}
cancelDisabled={isCreating}
primaryAction={{
label: isCreating ? 'Connecting…' : 'Connect & Sync',
onClick: handleSubmit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,10 @@ export function EditConnectorModal({
return (
<ChipModal
open={open}
onOpenChange={(val) => !isSaving && onOpenChange(val)}
onOpenChange={onOpenChange}
srTitle={`Edit ${displayName}`}
size='md'
dismissDisabled={isSaving}
>
<ChipModalHeader icon={Icon ?? null} onClose={() => onOpenChange(false)}>
Edit {displayName}
Expand Down Expand Up @@ -312,7 +313,6 @@ export function EditConnectorModal({
{activeTab === 'settings' && (
<ChipModalFooter
onCancel={() => onOpenChange(false)}
cancelDisabled={isSaving}
primaryAction={{
label: isSaving ? 'Saving…' : 'Save',
onClick: handleSave,
Expand Down
219 changes: 219 additions & 0 deletions packages/emcn/src/components/chip-modal/chip-modal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* @vitest-environment jsdom
*/
import { act, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Modal, ModalContent, ModalHeader } from '../modal/modal'
import { ChipConfirmModal, ChipModal, ChipModalFooter, ChipModalHeader } from './chip-modal'

vi.mock('next/navigation', () => ({
usePathname: () => '/workspace/workspace-1/home',
}))

let root: Root | null = null
let container: HTMLDivElement | null = null

function mount(ui: ReactNode) {
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
act(() => root?.render(ui))
}

afterEach(() => {
if (root) act(() => root?.unmount())
container?.remove()
root = null
container = null
})

/** The dialog panel Radix renders, which owns the Escape/outside-click handlers. */
function dialog(): HTMLElement {
const node = document.querySelector<HTMLElement>('[role="dialog"]')
if (!node) throw new Error('Dialog did not render')
return node
}

function buttonByText(text: string): HTMLButtonElement {
const match = Array.from(document.querySelectorAll('button')).find((button) =>
button.textContent?.includes(text)
)
if (!match) throw new Error(`No button containing "${text}"`)
return match as HTMLButtonElement
}

function closeButton(): HTMLButtonElement {
const match = Array.from(document.querySelectorAll('button')).find((button) =>
button.querySelector('.sr-only')?.textContent?.includes('Close')
)
if (!match) throw new Error('Close button did not render')
return match as HTMLButtonElement
}

function pressEscape() {
act(() => {
dialog().dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })
)
})
}

function Harness({
onOpenChange,
dismissDisabled,
}: {
onOpenChange: (open: boolean) => void
dismissDisabled?: boolean
}) {
return (
<ChipModal
open
onOpenChange={onOpenChange}
srTitle='Test modal'
dismissDisabled={dismissDisabled}
>
<ChipModalHeader onClose={() => onOpenChange(false)}>Title</ChipModalHeader>
<ChipModalFooter
onCancel={() => onOpenChange(false)}
primaryAction={{ label: 'Save', onClick: () => {} }}
/>
</ChipModal>
)
}

describe('ChipModal dismissDisabled', () => {
it('closes through every path when not set', () => {
const onOpenChange = vi.fn()
mount(<Harness onOpenChange={onOpenChange} />)

expect(closeButton().disabled).toBe(false)
expect(buttonByText('Cancel').disabled).toBe(false)

pressEscape()
expect(onOpenChange).toHaveBeenCalledWith(false)
})

// Outside-click is guarded by the same flag but jsdom cannot drive Radix's
// outside-interaction path, so asserting it here could never fail.
it('blocks the close button, Cancel and Escape when set', () => {
const onOpenChange = vi.fn()
mount(<Harness onOpenChange={onOpenChange} dismissDisabled />)

expect(closeButton().disabled).toBe(true)
expect(buttonByText('Cancel').disabled).toBe(true)

pressEscape()
expect(onOpenChange).not.toHaveBeenCalled()
})

// Either flag disables: an explicit `false` must not re-enable a button whose
// click Radix has already been told to ignore.
it('cannot be re-enabled by an explicit closeDisabled or cancelDisabled of false', () => {
const onOpenChange = vi.fn()
mount(
<ChipModal open onOpenChange={onOpenChange} srTitle='Test modal' dismissDisabled>
<ChipModalHeader onClose={() => onOpenChange(false)} closeDisabled={false}>
Title
</ChipModalHeader>
<ChipModalFooter
onCancel={() => onOpenChange(false)}
cancelDisabled={false}
primaryAction={{ label: 'Save', onClick: () => {} }}
/>
</ChipModal>
)

expect(closeButton().disabled).toBe(true)
expect(buttonByText('Cancel').disabled).toBe(true)
})

it('still lets an explicit true disable a button on its own', () => {
const onOpenChange = vi.fn()
mount(
<ChipModal open onOpenChange={onOpenChange} srTitle='Test modal'>
<ChipModalHeader onClose={() => onOpenChange(false)} closeDisabled>
Title
</ChipModalHeader>
<ChipModalFooter
onCancel={() => onOpenChange(false)}
primaryAction={{ label: 'Save', onClick: () => {} }}
/>
</ChipModal>
)

expect(closeButton().disabled).toBe(true)
expect(buttonByText('Cancel').disabled).toBe(false)
})
})

describe('ModalContent dismissDisabled', () => {
it('runs a consumer escape handler without letting it drop the guard', () => {
const onOpenChange = vi.fn()
const onEscapeKeyDown = vi.fn()
mount(
<Modal open onOpenChange={onOpenChange}>
<ModalContent srTitle='Guarded' dismissDisabled onEscapeKeyDown={onEscapeKeyDown}>
<input aria-label='Field' />
</ModalContent>
</Modal>
)

pressEscape()
expect(onEscapeKeyDown).toHaveBeenCalled()
expect(onOpenChange).not.toHaveBeenCalled()
})

it('disables the built-in ModalHeader close button', () => {
const onOpenChange = vi.fn()
mount(
<Modal open onOpenChange={onOpenChange}>
<ModalContent srTitle='Guarded' dismissDisabled>
<ModalHeader>Title</ModalHeader>
</ModalContent>
</Modal>
)

expect(closeButton().disabled).toBe(true)
})
})

describe('ChipConfirmModal pending', () => {
it('holds every exit shut while the confirm runs', () => {
const onOpenChange = vi.fn()
mount(
<ChipConfirmModal
open
onOpenChange={onOpenChange}
title='Delete key'
text='This cannot be undone.'
confirm={{ label: 'Delete', onClick: () => {}, pending: true, pendingLabel: 'Deleting...' }}
/>
)

expect(closeButton().disabled).toBe(true)
expect(buttonByText('Cancel').disabled).toBe(true)
expect(buttonByText('Deleting...').disabled).toBe(true)

pressEscape()
expect(onOpenChange).not.toHaveBeenCalled()
})

it('dismisses normally when the confirm is idle', () => {
const onOpenChange = vi.fn()
mount(
<ChipConfirmModal
open
onOpenChange={onOpenChange}
title='Delete key'
text='This cannot be undone.'
confirm={{ label: 'Delete', onClick: () => {} }}
/>
)

expect(closeButton().disabled).toBe(false)
act(() => closeButton().click())
expect(onOpenChange).toHaveBeenCalledWith(false)
})
})
Loading
Loading