Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5b9efe451 | ||
|
|
69fe6fcadd | ||
|
|
0ded15b363 | ||
|
|
708e53d829 | ||
|
|
e2c85b5af0 |
@@ -151,6 +151,40 @@ jobs:
|
||||
# npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
|
||||
# - name: Checking coverage
|
||||
# run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
|
||||
visual-regression-dispatch:
|
||||
needs: front-sb-build
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
- name: Package storybook
|
||||
run: tar -czf /tmp/storybook-twenty-front.tar.gz -C storybook-static .
|
||||
- name: Upload storybook tarball
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-front-tarball
|
||||
path: /tmp/storybook-twenty-front.tar.gz
|
||||
retention-days: 1
|
||||
- name: Dispatch to ci-privileged
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: visual-regression
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ github.event.pull_request.number }}",
|
||||
"run_id": "${{ github.run_id }}",
|
||||
"repo": "${{ github.repository }}",
|
||||
"project": "twenty-front",
|
||||
"branch": "${{ github.head_ref }}",
|
||||
"commit": "${{ github.event.pull_request.head.sha }}"
|
||||
}
|
||||
front-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
name: CI UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
if: github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-ui/**
|
||||
packages/twenty-shared/**
|
||||
ui-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/[email protected]
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: npx nx ${{ matrix.task }} twenty-ui
|
||||
ui-sb-build:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/[email protected]
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-ui
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
retention-days: 1
|
||||
ui-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: ui-sb-build
|
||||
env:
|
||||
STORYBOOK_URL: http://localhost:6007
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-shared
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-ui
|
||||
npx playwright install
|
||||
- name: Serve storybook & run tests
|
||||
run: |
|
||||
npx http-server packages/twenty-ui/storybook-static --port 6007 --silent &
|
||||
timeout 30 bash -c 'until curl -sf http://localhost:6007 > /dev/null 2>&1; do sleep 1; done'
|
||||
npx nx storybook:test twenty-ui
|
||||
visual-regression-dispatch:
|
||||
needs: ui-sb-build
|
||||
if: github.event_name == 'pull_request'
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: storybook-static
|
||||
- name: Package storybook
|
||||
run: tar -czf /tmp/storybook-twenty-ui.tar.gz -C storybook-static .
|
||||
- name: Upload storybook tarball
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui-tarball
|
||||
path: /tmp/storybook-twenty-ui.tar.gz
|
||||
retention-days: 1
|
||||
- name: Dispatch to ci-privileged
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: visual-regression
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ github.event.pull_request.number }}",
|
||||
"run_id": "${{ github.run_id }}",
|
||||
"repo": "${{ github.repository }}",
|
||||
"project": "twenty-ui",
|
||||
"branch": "${{ github.head_ref }}",
|
||||
"commit": "${{ github.event.pull_request.head.sha }}"
|
||||
}
|
||||
ci-ui-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
ui-task,
|
||||
ui-sb-build,
|
||||
ui-sb-test,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -15,6 +15,7 @@ const StyledTitle = styled.div<Pick<TitleProps, 'noMarginTop'>>`
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
margin-top: ${({ noMarginTop }) =>
|
||||
!noMarginTop ? themeCssVariables.spacing[4] : '0'};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export const Title = ({
|
||||
|
||||
+2
-1
@@ -27,8 +27,9 @@ export const MultiWorkspaceDropdownButton = () => {
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={MULTI_WORKSPACE_DROPDOWN_ID}
|
||||
dropdownOffset={{ y: -30, x: -5 }}
|
||||
dropdownOffset={{ y: -29, x: -5 }}
|
||||
clickableComponent={<MultiWorkspaceDropdownClickableComponent />}
|
||||
clickableComponentWidth="100%"
|
||||
dropdownComponents={<DropdownComponents />}
|
||||
onClose={() => {
|
||||
setMultiWorkspaceDropdown('default');
|
||||
|
||||
+7
-3
@@ -4,6 +4,7 @@ import {
|
||||
StyledContainer,
|
||||
StyledIconChevronDown,
|
||||
StyledLabel,
|
||||
StyledLabelWrapper,
|
||||
} from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspacesDropdownStyles';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -11,6 +12,7 @@ import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNaviga
|
||||
import { useContext } from 'react';
|
||||
import { Avatar } from 'twenty-ui/display';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const MultiWorkspaceDropdownClickableComponent = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
@@ -27,9 +29,11 @@ export const MultiWorkspaceDropdownClickableComponent = () => {
|
||||
placeholder={currentWorkspace?.displayName || ''}
|
||||
avatarUrl={currentWorkspace?.logo ?? DEFAULT_WORKSPACE_LOGO}
|
||||
/>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledLabelWrapper>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledLabel>{currentWorkspace?.displayName ?? ''}</StyledLabel>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
</StyledLabelWrapper>
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
|
||||
+12
-7
@@ -12,22 +12,27 @@ export const StyledContainer = styled.div<{
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${({ isNavigationDrawerExpanded }) =>
|
||||
isNavigationDrawerExpanded ? themeCssVariables.spacing[1] : '0'};
|
||||
height: ${themeCssVariables.spacing[5]};
|
||||
justify-content: space-between;
|
||||
isNavigationDrawerExpanded ? themeCssVariables.spacing[2] : '0'};
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
padding: calc(${themeCssVariables.spacing[1]} - 1px);
|
||||
width: ${({ isNavigationDrawerExpanded }) =>
|
||||
isNavigationDrawerExpanded ? '100%' : 'auto'};
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledLabelWrapper = styled.div`
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const StyledLabel = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledIconChevronDownContainer = styled.div<{ disabled?: boolean }>`
|
||||
|
||||
+3
-1
@@ -28,6 +28,7 @@ const StyledRightActions = styled.div<{ isExpanded: boolean }>`
|
||||
align-self: ${({ isExpanded }) => (isExpanded ? 'auto' : 'flex-end')};
|
||||
display: flex;
|
||||
flex-direction: ${({ isExpanded }) => (isExpanded ? 'row' : 'column')};
|
||||
flex-shrink: 0;
|
||||
gap: ${({ isExpanded }) => (isExpanded ? '0' : themeCssVariables.spacing[1])};
|
||||
margin-left: ${({ isExpanded }) => (isExpanded ? 'auto' : '0')};
|
||||
transition: gap calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
|
||||
@@ -44,8 +45,9 @@ const StyledNavigationDrawerCollapseButtonContainer = styled.div`
|
||||
const StyledWorkspaceDropdownContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
min-height: ${themeCssVariables.spacing[8]};
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
type NavigationDrawerHeaderProps = {
|
||||
|
||||
+8
-10
@@ -327,7 +327,6 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
escapedTable,
|
||||
escapedOldColumn,
|
||||
escapedNewColumn,
|
||||
escapedNewEnumType,
|
||||
escapedOldEnumType: `${escapedSchema}.${escapeIdentifier(oldEnumTypeName)}`,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
@@ -349,7 +348,6 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
escapedOldColumn,
|
||||
escapedSchema,
|
||||
escapedTable,
|
||||
escapedNewEnumType,
|
||||
escapedOldEnumType,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
@@ -358,7 +356,6 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
escapedTable: string;
|
||||
escapedOldColumn: string;
|
||||
escapedNewColumn: string;
|
||||
escapedNewEnumType: string;
|
||||
escapedOldEnumType: string;
|
||||
caseStatements: string;
|
||||
mappedValuesCondition: string;
|
||||
@@ -366,13 +363,14 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
return `
|
||||
UPDATE ${escapedSchema}.${escapedTable}
|
||||
SET ${escapedNewColumn} = (
|
||||
SELECT array_agg(
|
||||
CASE unnest_value::text
|
||||
${caseStatements}
|
||||
ELSE unnest_value::text::${escapedNewEnumType}
|
||||
END
|
||||
)
|
||||
FROM unnest(${escapedOldColumn}) AS unnest_value
|
||||
SELECT array_agg(mapped_value) FILTER (WHERE mapped_value IS NOT NULL)
|
||||
FROM (
|
||||
SELECT
|
||||
CASE unnest_value::text
|
||||
${caseStatements}
|
||||
END AS mapped_value
|
||||
FROM unnest(${escapedOldColumn}) AS unnest_value
|
||||
) enum_mapping
|
||||
)
|
||||
WHERE ${escapedOldColumn} IS NOT NULL
|
||||
AND ${escapedOldColumn} && ARRAY[${mappedValuesCondition}]::${escapedOldEnumType}[]`;
|
||||
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
import { createOneOperation } from 'test/integration/graphql/utils/create-one-operation.util';
|
||||
import { findOneOperation } from 'test/integration/graphql/utils/find-one-operation.util';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
const TEST_OBJECT_NAME_SINGULAR = 'testMultiSelectRemoval';
|
||||
const TEST_OBJECT_NAME_PLURAL = 'testMultiSelectRemovals';
|
||||
|
||||
// Reproduces the bug where removing a multi-select option fails when
|
||||
// existing records contain both the removed option and surviving options.
|
||||
describe('remove multi-select option with side effect on records', () => {
|
||||
let createdObjectMetadataId: string;
|
||||
let createdFieldMetadataId: string;
|
||||
let firstOptionId: string;
|
||||
let thirdOptionId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const { data: objectData } = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
nameSingular: TEST_OBJECT_NAME_SINGULAR,
|
||||
namePlural: TEST_OBJECT_NAME_PLURAL,
|
||||
labelSingular: 'Test Multi Select Removal',
|
||||
labelPlural: 'Test Multi Select Removals',
|
||||
icon: 'IconTestPipe',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
createdObjectMetadataId = objectData.createOneObject.id;
|
||||
|
||||
const { data: fieldData } = await createOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createdObjectMetadataId,
|
||||
type: FieldMetadataType.MULTI_SELECT,
|
||||
name: 'tagField',
|
||||
label: 'Tag Field',
|
||||
isLabelSyncedWithName: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'OPTION_1',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'OPTION_2',
|
||||
color: 'blue',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
label: 'Option 3',
|
||||
value: 'OPTION_3',
|
||||
color: 'red',
|
||||
position: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
options
|
||||
`,
|
||||
});
|
||||
|
||||
createdFieldMetadataId = fieldData.createOneField.id;
|
||||
const options = fieldData.createOneField.options;
|
||||
|
||||
if (!options) {
|
||||
throw new Error('Options should be defined');
|
||||
}
|
||||
|
||||
const option1 = options.find(
|
||||
(opt: { value: string }) => opt.value === 'OPTION_1',
|
||||
);
|
||||
const option2 = options.find(
|
||||
(opt: { value: string }) => opt.value === 'OPTION_2',
|
||||
);
|
||||
const option3 = options.find(
|
||||
(opt: { value: string }) => opt.value === 'OPTION_3',
|
||||
);
|
||||
|
||||
if (!option1?.id || !option2?.id || !option3?.id) {
|
||||
throw new Error('All options and their IDs should be defined');
|
||||
}
|
||||
|
||||
firstOptionId = option1.id;
|
||||
thirdOptionId = option3.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdObjectMetadataId,
|
||||
updatePayload: { isActive: false },
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: createdObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip the removed option from existing records that contain both surviving and removed values', async () => {
|
||||
// Record with OPTION_1 + OPTION_2 (OPTION_2 will be removed)
|
||||
const { data: mixedRecordData } = await createOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
input: { tagField: ['OPTION_1', 'OPTION_2'] },
|
||||
gqlFields: `id tagField`,
|
||||
});
|
||||
|
||||
const mixedRecordId = mixedRecordData.createOneResponse.id;
|
||||
|
||||
expect(mixedRecordData.createOneResponse.tagField).toEqual([
|
||||
'OPTION_1',
|
||||
'OPTION_2',
|
||||
]);
|
||||
|
||||
// Record with only OPTION_2 (the option that will be removed)
|
||||
const { data: removedOnlyData } = await createOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
input: { tagField: ['OPTION_2'] },
|
||||
gqlFields: `id tagField`,
|
||||
});
|
||||
|
||||
const removedOnlyRecordId = removedOnlyData.createOneResponse.id;
|
||||
|
||||
// Record with only surviving options
|
||||
const { data: survivingData } = await createOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
input: { tagField: ['OPTION_1', 'OPTION_3'] },
|
||||
gqlFields: `id tagField`,
|
||||
});
|
||||
|
||||
const survivingRecordId = survivingData.createOneResponse.id;
|
||||
|
||||
// Remove OPTION_2 -- keep OPTION_1 and OPTION_3
|
||||
const { data: updateData, errors: updateErrors } =
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createdFieldMetadataId,
|
||||
updatePayload: {
|
||||
options: [
|
||||
{
|
||||
id: firstOptionId,
|
||||
label: 'Option 1',
|
||||
value: 'OPTION_1',
|
||||
color: 'green',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: thirdOptionId,
|
||||
label: 'Option 3',
|
||||
value: 'OPTION_3',
|
||||
color: 'red',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
gqlFields: `id options`,
|
||||
});
|
||||
|
||||
expect(updateErrors).toBeUndefined();
|
||||
expect(updateData.updateOneField.options).toHaveLength(2);
|
||||
|
||||
// Mixed record: OPTION_2 should be stripped, only OPTION_1 remains
|
||||
const { data: mixedAfter } = await findOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
gqlFields: `id tagField`,
|
||||
filter: { id: { eq: mixedRecordId } },
|
||||
});
|
||||
|
||||
expect(mixedAfter.findResponse.tagField).toEqual(['OPTION_1']);
|
||||
|
||||
// Removed-only record: all values were removed, field is empty
|
||||
const { data: removedOnlyAfter } = await findOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
gqlFields: `id tagField`,
|
||||
filter: { id: { eq: removedOnlyRecordId } },
|
||||
});
|
||||
|
||||
const removedOnlyTagField = removedOnlyAfter.findResponse.tagField;
|
||||
|
||||
expect(
|
||||
removedOnlyTagField === null || removedOnlyTagField.length === 0,
|
||||
).toBe(true);
|
||||
|
||||
// Surviving record: should be unchanged
|
||||
const { data: survivingAfter } = await findOneOperation({
|
||||
expectToFail: false,
|
||||
objectMetadataSingularName: TEST_OBJECT_NAME_SINGULAR,
|
||||
gqlFields: `id tagField`,
|
||||
filter: { id: { eq: survivingRecordId } },
|
||||
});
|
||||
|
||||
expect(survivingAfter.findResponse.tagField).toEqual([
|
||||
'OPTION_1',
|
||||
'OPTION_3',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ const StyledLayout = styled.div<{
|
||||
|
||||
type ComponentStorybookLayoutProps = {
|
||||
width?: number;
|
||||
backgroundColor?: string | undefined;
|
||||
backgroundColor?: string;
|
||||
height?: number;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import { themeCssVariables } from '@ui/theme-constants';
|
||||
|
||||
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
|
||||
|
||||
const getBackgroundColor = (inverted: boolean, accent: string) => {
|
||||
const getBackgroundColor = (
|
||||
inverted: boolean,
|
||||
accent: string,
|
||||
): string | undefined => {
|
||||
if (!inverted) return undefined;
|
||||
|
||||
switch (accent) {
|
||||
|
||||
Reference in New Issue
Block a user