diff --git a/.github/workflows/prjob_cypress_tests.yml b/.github/workflows/prjob_cypress_tests.yml index bfb395f5..5ff88420 100644 --- a/.github/workflows/prjob_cypress_tests.yml +++ b/.github/workflows/prjob_cypress_tests.yml @@ -69,6 +69,13 @@ jobs: run: | sleep 20 yarn run cypress:run + + - name: Upload Cypress logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-logs + path: cypress/screenshots - name: Stop Stack working-directory: ./stack diff --git a/cypress.config.ts b/cypress.config.ts index 257d3b09..7047c4c0 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from "cypress"; export default defineConfig({ - viewportWidth: 1600, - viewportHeight: 1000, + viewportWidth: 1800, + viewportHeight: 1200, e2e: { setupNodeEvents(on, config) { // implement node event listeners here diff --git a/cypress/e2e/31_editBountyBySearch.cy.ts b/cypress/e2e/31_editBountyBySearch.cy.ts index 7fd55feb..52209382 100644 --- a/cypress/e2e/31_editBountyBySearch.cy.ts +++ b/cypress/e2e/31_editBountyBySearch.cy.ts @@ -101,7 +101,6 @@ describe('Edit Bounty By Searching, Change Workspace And Assignee', () => { cy.wait(600); cy.contains('Open').click(); - cy.contains('Assigned').click(); cy.get('body').click(0, 0); cy.wait(1000); diff --git a/cypress/e2e/33_workspaceBountyStatusFilter.cy.ts b/cypress/e2e/33_workspaceBountyStatusFilter.cy.ts index ce3a281a..cafcfcf1 100644 --- a/cypress/e2e/33_workspaceBountyStatusFilter.cy.ts +++ b/cypress/e2e/33_workspaceBountyStatusFilter.cy.ts @@ -191,15 +191,18 @@ describe('filter by status for org bounty', () => { cy.contains(bounty6.title); cy.wait(1000); - cy.contains('label', 'Assigned').click(); - cy.wait(1000); - cy.create_workspace_bounty(bounty7); cy.wait(1000); cy.create_workspace_bounty(bounty8); cy.wait(1000); + cy.contains('Status').first().click(); + cy.wait(1000); + + cy.contains('label', 'Assigned').click(); + cy.wait(1000); + cy.contains(bounty7.title).click(); cy.wait(1000); diff --git a/cypress/e2e/37_bountyFilters.cy.ts b/cypress/e2e/37_bountyFilters.cy.ts index 692522ab..e4892b45 100644 --- a/cypress/e2e/37_bountyFilters.cy.ts +++ b/cypress/e2e/37_bountyFilters.cy.ts @@ -1,22 +1,18 @@ -describe('Alice tries to create 8 bounties and then assert filtered bounties', () => { - it('Create 8 bounties and assert filtered bounties', () => { - let activeUser = 'alice'; +describe('Alice tries to create 6 bounties and then assert filtered bounties', () => { + it('Create 6 bounties and assert filtered bounties', () => { + let activeUser = 'bob'; cy.login(activeUser); - cy.wait(1000); + cy.wait(2000); + + const assignees = ['carol', 'carol', 'carol', 'carol', '', '']; + const languages = ['Typescript', 'Lightning', 'PHP', 'Typescript', 'PHP', 'Typescript']; - const assignees = ['carol', 'carol', 'carol', '', '', '', 'carol', 'carol']; - const languages = [ - 'Typescript', - 'Lightning', - 'PHP', - 'Typescript', - 'PHP', - 'Typescript', - 'Lightning', - 'Typescript' - ]; - - for (let i = 0; i < 8; i++) { + cy.intercept({ + method: 'GET', + url: 'http://localhost:13000/gobounties/all*' + }).as('bountyFilter'); + + for (let i = 0; i < 6; i++) { cy.create_bounty({ title: `Filter Bounty Title ${i}`, category: 'Web development', @@ -31,76 +27,72 @@ describe('Alice tries to create 8 bounties and then assert filtered bounties', ( }); cy.wait(1000); - if (i > 5) { - if (i === 6) { + if (i > 2) { + if (i === 3) { // Unchecked the Open Filter and Checked the Assigned Filter cy.contains('Filter').click(); - cy.contains('Open').click(); - cy.contains('Assigned').click(); + cy.contains('label', 'Open').click(); + cy.contains('label', 'Assigned').click(); cy.wait(1000); - } - cy.contains(`Filter Bounty Title ${i}`).click(); - cy.wait(1000); + cy.contains(`Filter Bounty Title ${i}`).click(); + cy.wait(1000); - cy.contains('Mark as Paid').click(); - cy.wait(1000); + cy.contains('Mark as Paid').click(); + cy.wait(1000); - cy.contains('Next').click(); - cy.wait(1000); + cy.contains('Next').click(); + cy.wait(1000); - cy.contains('Skip and Mark Paid').click(); - cy.wait(1000); + cy.contains('Skip and Mark Paid').click(); + cy.wait(1000); - cy.get('body').click(0, 0); - cy.wait(1000); + cy.get('body').click(0, 0); + } } } cy.logout(activeUser); cy.wait(1000); - // Unchecked the Open Filter and Checked the Assigned Filter - cy.contains('Filter').click(); - cy.contains('Open').click(); - cy.contains('Assigned').click(); + // check open filter cy.contains('Filter').click(); + cy.contains('label', 'Assigned').click(); + cy.contains('label', 'Open').click(); cy.wait(1000); + // close filter + cy.get('body').click(0, 0); + + cy.wait('@bountyFilter'); + + // assigned bounties should not exists + cy.contains(`Filter Bounty Title 0`).should('not.exist'); + cy.contains(`Filter Bounty Title 1`).should('not.exist'); - for (let i = 0; i < 8; i++) { - if (i < 3) { - cy.contains(`Filter Bounty Title ${i}`).should('exist'); - } else { - cy.contains(`Filter Bounty Title ${i}`).should('not.exist'); - } - } cy.wait(1000); cy.contains('Filter').click(); cy.contains('label', 'Typescript').click(); cy.contains('Filter').click(); - cy.wait(1000); - for (let i = 0; i < 8; i++) { - if (i == 0) { - cy.contains(`Filter Bounty Title ${i}`).should('exist'); - } else { - cy.contains(`Filter Bounty Title ${i}`).should('not.exist'); - } - } + // close filter + cy.get('body').click(0, 0); + cy.wait('@bountyFilter'); + + cy.contains(`Filter Bounty Title 5`).should('exist'); cy.wait(1000); cy.contains('Filter').click(); - cy.contains('label', 'Lightning').click(); - cy.contains('Filter').click(); + // uncheck + cy.contains('label', 'Open').click(); + cy.contains('label', 'Typescript').click(); cy.wait(1000); - for (let i = 0; i < 8; i++) { - if (i < 2) { - cy.contains(`Filter Bounty Title ${i}`).should('exist'); - } else { - cy.contains(`Filter Bounty Title ${i}`).should('not.exist'); - } - } + // check + cy.contains('label', 'Assigned').click(); + cy.contains('label', 'Lightning').click(); + cy.wait('@bountyFilter'); + + cy.contains(`Filter Bounty Title 1`).should('exist'); }); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 0c81ee74..7828d9fb 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -160,15 +160,15 @@ Cypress.Commands.add('logout', (userAlias: string) => { Cypress.Commands.add('create_bounty', (bounty, clickMethod = 'contains') => { if (clickMethod === 'contains') { - cy.contains('Bounties').click(); + cy.contains('Bounties').click({ force: true }); } else if (clickMethod === 'testId') { - cy.get('[data-testid="Bounties-tab"]').click(); + cy.get('[data-testid="Bounties-tab"]').click({ force: true }); } else { throw new Error('Invalid click method specified'); } cy.wait(5000); - cy.contains('Post a Bounty').click(); - cy.contains('Start').click(); + cy.contains('Post a Bounty').click({ force: true }); + cy.contains('Start').click({ force: true }); if (bounty.workspace) { cy.get('[data-testid="Workspace"]').click({ force: true }); @@ -181,16 +181,18 @@ Cypress.Commands.add('create_bounty', (bounty, clickMethod = 'contains') => { if (bounty.github_issue_url) { cy.get('[data-testid="Github"]').type(bounty.github_issue_url); } - cy.wait(1000); if (bounty.coding_language && bounty.coding_language.length > 0) { - cy.contains('Coding Language').click(); + cy.contains('Coding Language').click({ force: true }); for (let i = 0; i < bounty.coding_language.length; i++) { - cy.get('.CheckboxOuter').contains(bounty.coding_language[i]).scrollIntoView().click(); + cy.get('.CheckboxOuter') + .contains(bounty.coding_language[i]) + .scrollIntoView() + .click({ force: true }); } - cy.contains('Coding Language').click(); + cy.contains('Coding Language').click({ force: true }); } cy.get('[data-testid="Category *"]').click(); @@ -228,10 +230,10 @@ Cypress.Commands.add('create_bounty', (bounty, clickMethod = 'contains') => { if (bounty.assign) { cy.get('.SearchInput').type(bounty.assign); - cy.wait(1000); - cy.get('.People').contains('Assign').click(); + cy.wait(2000); + cy.get('.People').contains('Assign').click({ force: true }); } else { - cy.contains('Decide Later').click(); + cy.contains('Decide Later').click({ force: true }); } cy.contains('Finish').click(); diff --git a/src/pages/tickets/Tickets.tsx b/src/pages/tickets/Tickets.tsx index ef34f3d9..bb92a9c9 100644 --- a/src/pages/tickets/Tickets.tsx +++ b/src/pages/tickets/Tickets.tsx @@ -24,7 +24,7 @@ function BodyComponent() { const [showDropdown, setShowDropdown] = useState(false); const selectedWidget = 'bounties'; const [scrollValue, setScrollValue] = useState(false); - const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState(defaultBountyStatus); + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState(main.bountiesStatus); const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({}); const [languageString, setLanguageString] = useState(''); const [page, setPage] = useState(1); @@ -41,24 +41,32 @@ function BodyComponent() { useEffect(() => { const status = searchParams.get('status'); if (status === 'completed') { - setCheckboxIdToSelectedMap({ + const status = { ...defaultBountyStatus, Open: false, Completed: true - }); + }; + setCheckboxIdToSelectedMap(status); + main.setBountiesStatus(status); } else if (status === 'assigned') { - setCheckboxIdToSelectedMap({ + const status = { ...defaultBountyStatus, Open: false, Assigned: true - }); + }; + setCheckboxIdToSelectedMap(status); + main.setBountiesStatus(status); } else if (status === 'open') { - setCheckboxIdToSelectedMap({ + const status = { ...defaultBountyStatus, Open: true - }); + }; + setCheckboxIdToSelectedMap(status); + main.setBountiesStatus(status); + } else { + setCheckboxIdToSelectedMap(main.bountiesStatus); } - }, [searchParams, loading]); + }, [searchParams, loading, main.bountiesStatus]); useEffect(() => { (async () => { @@ -104,9 +112,9 @@ function BodyComponent() { [optionId]: !checkboxIdToSelectedMap[optionId] } }; - setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); // set the store status, to enable the accurate navigation modal call main.setBountiesStatus(newCheckboxIdToSelectedMap); + setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); getTotalBounties(newCheckboxIdToSelectedMap); // set data to default setCurrentItems(queryLimit); diff --git a/src/pages/tickets/__tests__/Tickets.spec.tsx b/src/pages/tickets/__tests__/Tickets.spec.tsx index 5c26778b..a5fcec69 100644 --- a/src/pages/tickets/__tests__/Tickets.spec.tsx +++ b/src/pages/tickets/__tests__/Tickets.spec.tsx @@ -5,6 +5,7 @@ import { mainStore } from 'store/main'; import { uiStore } from 'store/ui'; import { user } from '__test__/__mockData__/user'; import Tickets from '../Tickets'; +import { BountyStatus, defaultBountyStatus } from 'store/interface'; let fetchStub: sinon.SinonStub; @@ -35,6 +36,8 @@ jest.mock('remark-gfm', () => {}); // eslint-disable-next-line @typescript-eslint/no-empty-function jest.mock('rehype-raw', () => {}); +jest.setTimeout(10000); + const mockPush = jest.fn(); const mockGoBack = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -60,7 +63,8 @@ jest.mock('../../../store', () => ({ getTotalBountyCount: jest.fn(), setBountiesStatus: jest.fn(), setBountyLanguages: jest.fn(), - getTribesByOwner: jest.fn() + getTribesByOwner: jest.fn(), + bountiesStatus: defaultBountyStatus }, ui: { meInfo: {}, diff --git a/src/pages/tickets/workspace/WorkspaceTickets.tsx b/src/pages/tickets/workspace/WorkspaceTickets.tsx index 08b69194..cf41c1bf 100644 --- a/src/pages/tickets/workspace/WorkspaceTickets.tsx +++ b/src/pages/tickets/workspace/WorkspaceTickets.tsx @@ -15,19 +15,15 @@ import { WorkspaceHeader } from './workspaceHeader'; function WorkspaceBodyComponent() { const { main, ui } = useStores(); + const { uuid } = useParams<{ uuid: string; bountyId: string }>(); const [loading, setLoading] = useState(true); const [showDropdown, setShowDropdown] = useState(false); const selectedWidget = 'bounties'; const [scrollValue, setScrollValue] = useState(false); - - const item = localStorage.getItem('workspaceBountyStatus'); - const savedStatus = item ? JSON.parse(item) : null; // or provide a default value other than null - - const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState(savedStatus); - + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState( + main.workspaceBountiesStatus + ); const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({}); - const { uuid } = useParams<{ uuid: string; bountyId: string }>(); - const [workspaceData, setWorkspaceData] = useState(); const [languageString, setLanguageString] = useState(''); const [page, setPage] = useState(1); @@ -35,25 +31,23 @@ function WorkspaceBodyComponent() { const [WorkspaceTotalBounties, setTotalBounties] = useState(0); const color = colors['light']; - const history = useHistory(); const isMobile = useIsMobile(); + useEffect(() => { + setCheckboxIdToSelectedMap(main.workspaceBountiesStatus); + }, [main.workspaceBountiesStatus]); + useEffect(() => { (async () => { if (!uuid) return; const workspaceData = await main.getUserWorkspaceByUuid(uuid); if (!workspaceData) return; setWorkspaceData(workspaceData); - setLoading(false); })(); }, [main, uuid, checkboxIdToSelectedMap, languageString]); - useEffect(() => { - setCheckboxIdToSelectedMap({ ...savedStatus }); - }, [loading]); - useEffect(() => { if (ui.meInfo) { main.getTribesByOwner(ui.meInfo.owner_pubkey || ''); @@ -67,7 +61,6 @@ function WorkspaceBodyComponent() { ...statusData, resetPage: true }); - setTotalBounties(WorkspaceTotalBounties); }, [main] @@ -84,10 +77,9 @@ function WorkspaceBodyComponent() { [optionId]: !checkboxIdToSelectedMap[optionId] } }; - setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); - localStorage.setItem('orgBountyStatus', JSON.stringify(newCheckboxIdToSelectedMap)); // set the store status, to enable the accurate navigation modal call - main.setBountiesStatus(newCheckboxIdToSelectedMap); + main.setWorkspaceBountiesStatus(newCheckboxIdToSelectedMap); + setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); getTotalBounties(uuid, newCheckboxIdToSelectedMap, page); // set data to default setCurrentItems(queryLimit); diff --git a/src/people/widgetViews/WidgetSwitchViewer.tsx b/src/people/widgetViews/WidgetSwitchViewer.tsx index cb2c21ef..a9eb3e62 100644 --- a/src/people/widgetViews/WidgetSwitchViewer.tsx +++ b/src/people/widgetViews/WidgetSwitchViewer.tsx @@ -96,7 +96,6 @@ function WidgetSwitchViewer(props: any) { const WorkspaceBountiesTotal = WorkspaceTotalBounties ?? 0; const phaseBountiesTotal = phaseTotalBounties ?? 0; const page = propsPage ?? 0; - const panelStyles = isMobile ? { minHeight: 132 @@ -111,7 +110,6 @@ function WidgetSwitchViewer(props: any) { }; const { peoplePosts, peopleBounties, peopleOffers } = main; - const { selectedWidget, onPanelClick, org_uuid } = props; if (!selectedWidget) { diff --git a/src/people/widgetViews/WorkspaceView.tsx b/src/people/widgetViews/WorkspaceView.tsx index 7f13dbbc..51df4dda 100644 --- a/src/people/widgetViews/WorkspaceView.tsx +++ b/src/people/widgetViews/WorkspaceView.tsx @@ -166,6 +166,7 @@ const Workspaces = (props: { person: Person }) => { const [detailsOpen, setDetailsOpen] = useState(false); const [workspace, setWorkspace] = useState(); const { main, ui } = useStores(); + const [totalInvoicees, setTotalInvoices] = useState(0); const isMobile = useIsMobile(); const config = widgetConfigs['workspaces']; const isMyProfile = ui?.meInfo?.pubkey === props?.person?.owner_pubkey; @@ -217,37 +218,46 @@ const Workspaces = (props: { person: Person }) => { }, 2000); }, []); + const getUserInvoicesCount = useCallback(async () => { + const count = await main.allUserWorkspaceInvoiceCount(); + setTotalInvoices(count); + }, [main]); + + useEffect(() => { + getUserInvoicesCount(); + }, [getUserInvoicesCount]); + useEffect(() => { - if (!detailsOpen) { + if (!detailsOpen && !isOpen && totalInvoicees > 0) { pollAllInvoices(); } return () => { clearInterval(interval); }; - }, [pollAllInvoices, detailsOpen]); + }, [pollAllInvoices, detailsOpen, isOpen, totalInvoicees]); // renders org as list item - const orgUi = (org: any, key: number) => { - const btnDisabled = (!org.bounty_count && org.bount_count !== 0) || !org.uuid; + const workspaceUi = (space: any, key: number) => { + const btnDisabled = (!space.bounty_count && space.bount_count !== 0) || !space.uuid; return ( - - + + {user_pubkey && ( { - setWorkspace(org); + setWorkspace(space); setDetailsOpen(true); }} /> )} window.open(`/workspace/bounties/${org.uuid}`, '_target')} + onClick={() => window.open(`/workspace/bounties/${space.uuid}`, '_target')} > View Bounties @@ -276,7 +286,7 @@ const Workspaces = (props: { person: Person }) => { )} - {main.workspaces.map((org: Workspace, i: number) => orgUi(org, i))} + {main.workspaces.map((space: Workspace, i: number) => workspaceUi(space, i))} ); diff --git a/src/store/main.ts b/src/store/main.ts index 72cdfe9e..50f02833 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -572,26 +572,23 @@ export class MainStore { @persist('object') bountiesStatus: BountyStatus = defaultBountyStatus; - @action setBountiesStatus(status: BountyStatus) { this.bountiesStatus = status; } + @persist('object') workspaceBountiesStatus: BountyStatus = defaultWorkspaceBountyStatus; - @action setWorkspaceBountiesStatus(status: BountyStatus) { this.workspaceBountiesStatus = status; } @persist('object') bountyLanguages = ''; - @action setBountyLanguages(languages: string) { this.bountyLanguages = languages; } getWantedsPrevParams?: QueryParams = {}; - async getPeopleBounties(params?: QueryParams): Promise { const queryParams: QueryParams = { limit: queryLimit,