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
138 changes: 79 additions & 59 deletions cypress/component/features/notes/NoteEditor.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,27 @@ import React from 'react'
import type { SupabaseClient } from '@supabase/supabase-js'
import { NoteEditor } from '../../../../ui/web/components/features/notes/NoteEditor'
import { SupabaseTestProvider } from '../../../../ui/web/providers/SupabaseProvider'
import { createSupabaseForExportDialog } from './noteTestHelpers'

const createSupabaseForExportDialog = () => {
const invoke = cy.stub().callsFake((name: string, params: { body: { action?: string } }) => {
if (name === 'wordpress-settings-status') {
return Promise.resolve({
data: {
configured: true,
integration: {
siteUrl: 'https://stage.dkoreiba.com/',
wpUsername: 'editor',
enabled: true,
hasPassword: true,
},
},
error: null,
})
}
if (name === 'wordpress-bridge' && params.body.action === 'get_categories') {
return Promise.resolve({
data: {
categories: [{ id: 1, name: 'Tech' }],
rememberedCategoryIds: [],
},
error: null,
})
}
return Promise.resolve({ data: null, error: null })
})

const supabase = {
functions: { invoke },
/** Minimal Supabase stub for tests that open the "..." menu but don't need real WP/RAG responses.
* With user=null (SupabaseTestProvider default), useRagStatus returns early without querying. */
function createMinimalSupabase(): SupabaseClient {
return {
functions: { invoke: cy.stub().resolves({ data: null, error: null }) },
auth: {
getUser: cy.stub().resolves({ data: { user: { id: 'user-1' } } }),
getUser: cy.stub().resolves({ data: { user: null }, error: null }),
getSession: cy.stub().resolves({ data: { session: null }, error: null }),
onAuthStateChange: cy.stub().returns({ data: { subscription: { unsubscribe: cy.stub() } } }),
signOut: cy.stub().resolves({ error: null }),
},
from: cy.stub().returns({
upsert: cy.stub().resolves({ error: null }),
update: cy.stub().returnsThis(),
eq: cy.stub().resolves({ error: null }),
select: cy.stub().returns({
eq: cy.stub().returns({
eq: cy.stub().resolves({ data: [], error: null }),
}),
}),
}),
} as unknown as SupabaseClient

return { supabase, invoke }
}

describe('NoteEditor Component', () => {
Expand Down Expand Up @@ -396,36 +375,37 @@ describe('NoteEditor Component', () => {
cy.get('[data-cy="redo-button"]').should('be.disabled')
})

it('shows export button when WordPress is configured and note has id', () => {
const props = {
...getDefaultProps(),
noteId: 'note-1',
wordpressConfigured: true,
}

it('shows more actions menu when note has id', () => {
const props = { ...getDefaultProps(), noteId: 'note-1' }
cy.mount(<NoteEditor {...props} />)
cy.contains('button', 'Export to WP').should('be.visible')
cy.get('button[aria-label="More actions"]').should('be.visible')
})

it('shows mobile more-actions menu instead of visible export button', () => {
cy.viewport(390, 844)
it('does not show more actions menu for new notes without id', () => {
cy.mount(<NoteEditor {...getDefaultProps()} />)
cy.get('button[aria-label="More actions"]').should('not.exist')
})

it('shows WordPress export inside the more actions menu when configured', () => {
const { supabase } = createSupabaseForExportDialog()
const props = {
...getDefaultProps(),
noteId: 'note-1',
wordpressConfigured: true,
}

cy.mount(<NoteEditor {...props} />)
cy.contains('button', 'Export to WP')
.should('have.class', 'hidden')
.and('have.class', 'md:inline-flex')
cy.get('button[aria-label="More actions"]').should('be.visible')
cy.mount(
<SupabaseTestProvider supabase={supabase}>
<NoteEditor {...props} />
</SupabaseTestProvider>
)
// WP export is no longer an inline header button
cy.contains('button', 'Export to WP').should('not.exist')
// It lives in the "..." menu
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Export to WP').should('be.visible')
})

it('opens export dialog from mobile menu and closes menu content', () => {
cy.viewport(390, 844)

it('opens export dialog from the more actions menu', () => {
const props = {
...getDefaultProps(),
noteId: 'note-1',
Expand All @@ -449,14 +429,54 @@ describe('NoteEditor Component', () => {
})
})

it('hides export button for new notes without id', () => {
it('does not show export button when WordPress is not configured or note has no id', () => {
// With wordpressConfigured=true but no noteId — no menu at all
cy.mount(<NoteEditor {...getDefaultProps()} wordpressConfigured={true} />)
cy.contains('button', 'Export to WP').should('not.exist')
cy.get('button[aria-label="More actions"]').should('not.exist')
})

it('shows delete note option in the more actions menu when onDelete is provided', () => {
const props = {
...getDefaultProps(),
wordpressConfigured: true,
noteId: 'note-1',
onDelete: cy.stub(),
}
cy.mount(
<SupabaseTestProvider supabase={createMinimalSupabase()}>
<NoteEditor {...props} />
</SupabaseTestProvider>
)
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Delete note').should('be.visible')
})

cy.mount(<NoteEditor {...props} />)
cy.contains('button', 'Export to WP').should('not.exist')
it('calls onDelete when delete note is clicked from the more actions menu', () => {
const onDelete = cy.stub().as('onDelete')
const props = {
...getDefaultProps(),
noteId: 'note-1',
onDelete,
}
cy.mount(
<SupabaseTestProvider supabase={createMinimalSupabase()}>
<NoteEditor {...props} />
</SupabaseTestProvider>
)
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Delete note').click()
cy.get('@onDelete').should('have.been.calledOnce')
})

it('does not show delete note option when onDelete is not provided', () => {
const props = { ...getDefaultProps(), noteId: 'note-1' }
cy.mount(
<SupabaseTestProvider supabase={createMinimalSupabase()}>
<NoteEditor {...props} />
</SupabaseTestProvider>
)
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Delete note').should('not.exist')
})
})

Expand Down
118 changes: 57 additions & 61 deletions cypress/component/features/notes/NoteView.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,8 @@
import React from 'react'
import type { SupabaseClient } from '@supabase/supabase-js'
import { NoteView } from '../../../../ui/web/components/features/notes/NoteView'
import type { Note } from '../../../../core/types/domain'
import { SupabaseTestProvider } from '../../../../ui/web/providers/SupabaseProvider'

const createSupabaseForExportDialog = () => {
const invoke = cy.stub().callsFake((name: string, params: { body: { action?: string } }) => {
if (name === 'wordpress-settings-status') {
return Promise.resolve({
data: {
configured: true,
integration: {
siteUrl: 'https://stage.dkoreiba.com/',
wpUsername: 'editor',
enabled: true,
hasPassword: true,
},
},
error: null,
})
}
if (name === 'wordpress-bridge' && params.body.action === 'get_categories') {
return Promise.resolve({
data: {
categories: [{ id: 1, name: 'Tech' }],
rememberedCategoryIds: [],
},
error: null,
})
}
return Promise.resolve({ data: null, error: null })
})

const supabase = {
functions: { invoke },
auth: {
getUser: cy.stub().resolves({ data: { user: { id: 'user-1' } } }),
},
from: cy.stub().returns({
upsert: cy.stub().resolves({ error: null }),
update: cy.stub().returnsThis(),
eq: cy.stub().resolves({ error: null }),
}),
} as unknown as SupabaseClient

return { supabase, invoke }
}
import { createSupabaseForExportDialog } from './noteTestHelpers'

describe('NoteView Component', () => {
const mockNote: Note & { content?: string | null } = {
Expand Down Expand Up @@ -129,42 +86,76 @@ describe('NoteView Component', () => {
cy.get('.prose script').should('not.exist')
})

it('shows export button when WordPress is configured', () => {
it('more actions menu is always visible', () => {
// The "..." button is always present — it holds RAG controls (and optionally WP export)
const props = {
note: mockNote,
onEdit: cy.stub(),
onDelete: cy.stub(),
onTagClick: cy.stub(),
onRemoveTag: cy.stub(),
wordpressConfigured: true,
wordpressConfigured: false,
}

cy.mount(<NoteView {...props} />)
cy.contains('button', 'Export to WP').should('be.visible')
cy.get('button[aria-label="More actions"]').should('be.visible')
})

it('shows mobile more-actions menu instead of visible export button', () => {
it('more actions menu is visible on mobile too', () => {
cy.viewport(390, 844)

const props = {
note: mockNote,
onEdit: cy.stub(),
onDelete: cy.stub(),
onTagClick: cy.stub(),
onRemoveTag: cy.stub(),
wordpressConfigured: true,
}

cy.mount(<NoteView {...props} />)
cy.contains('button', 'Export to WP')
.should('have.class', 'hidden')
.and('have.class', 'md:inline-flex')
cy.get('button[aria-label="More actions"]').should('be.visible')
})

it('opens export dialog from mobile menu and closes menu content', () => {
cy.viewport(390, 844)
it('shows RAG index controls inside the more actions menu', () => {
const { supabase } = createSupabaseForExportDialog()
const props = {
note: mockNote,
onEdit: cy.stub(),
onDelete: cy.stub(),
onTagClick: cy.stub(),
onRemoveTag: cy.stub(),
}
cy.mount(
<SupabaseTestProvider supabase={supabase}>
<NoteView {...props} />
</SupabaseTestProvider>
)
cy.get('button[aria-label="More actions"]').click()
// RAG items always present in the menu
cy.contains('[role="menuitem"]', 'Index note').should('be.visible')
cy.get('[data-cy="note-delete-index-button"]').should('be.visible')
})

it('shows WordPress export inside the more actions menu when configured', () => {
const { supabase } = createSupabaseForExportDialog()
const props = {
note: mockNote,
onEdit: cy.stub(),
onDelete: cy.stub(),
onTagClick: cy.stub(),
onRemoveTag: cy.stub(),
wordpressConfigured: true,
}
cy.mount(
<SupabaseTestProvider supabase={supabase}>
<NoteView {...props} />
</SupabaseTestProvider>
)
// WP export is no longer an inline header button
cy.contains('button', 'Export to WP').should('not.exist')
// It lives in the "..." menu
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Export to WP').should('be.visible')
})

it('opens export dialog from the more actions menu', () => {
const props = {
note: mockNote,
onEdit: cy.stub(),
Expand Down Expand Up @@ -192,7 +183,8 @@ describe('NoteView Component', () => {
})
})

it('hides export button when WordPress is not configured', () => {
it('does not show WordPress export in menu when not configured', () => {
const { supabase } = createSupabaseForExportDialog()
const props = {
note: mockNote,
onEdit: cy.stub(),
Expand All @@ -201,8 +193,12 @@ describe('NoteView Component', () => {
onRemoveTag: cy.stub(),
wordpressConfigured: false,
}

cy.mount(<NoteView {...props} />)
cy.contains('button', 'Export to WP').should('not.exist')
cy.mount(
<SupabaseTestProvider supabase={supabase}>
<NoteView {...props} />
</SupabaseTestProvider>
)
cy.get('button[aria-label="More actions"]').click()
cy.contains('[role="menuitem"]', 'Export to WP').should('not.exist')
})
})
Loading
Loading