Skip to content

Commit

Permalink
chore: circleci setup to allow pre-release on next branch [SPA-1634] (#…
Browse files Browse the repository at this point in the history
…176)

* chore: circleci setup to allow pre-release on next branch

* feat!: register toolkit components and include wrap container by default [ALT-115] (#175)

BREAKING CHANGE: Registered components will be wrapped with a div container by default. The `wrapComponent` option can be used when registering components to opt-out of this behavior if necessary.

* chore(release): updated release notes and package versions [ci skip]

 - @contentful/experience-builder@3.0.0
 - @contentful/experience-builder-types@2.0.0

* fix(experience-builder-types): extend variables type on component definition [ALT-57] (#178)

* chore(release): updated release notes and package versions [ci skip]

 - @contentful/experience-builder@3.0.1
 - @contentful/experience-builder-types@2.0.1

---------

Co-authored-by: Adrian Meyer <8539634+primeinteger@users.noreply.github.com>
Co-authored-by: contentful-automation[bot] <100587065+contentful-automation[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 8, 2023
1 parent f95acdb commit 9b6b5b3
Show file tree
Hide file tree
Showing 13 changed files with 2,966 additions and 2,933 deletions.
32 changes: 25 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
path: ./packages/*/reports/clover.xml

# Do not use the workspace for the release step
release:
release-prod:
executor: docker-with-node
steps:
- checkout
Expand All @@ -79,8 +79,19 @@ jobs:
- vault/configure-lerna
- run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc
- run: yarn build # This should not be needed, but without it it will not contain any files
- run: yarn lerna version --no-private --conventional-commits --create-release
github --yes
- run: yarn lerna version --no-private --conventional-commits --create-release github --yes
- run: yarn lerna publish from-git --yes

release-next:
executor: docker-with-node
steps:
- checkout
- use-vault
- install-dependencies
- vault/configure-lerna
- run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc
- run: yarn build # This should not be needed, but without it it will not contain any files
- run: yarn lerna version --no-private --conventional-commits --conventional-prerelease --preid next --create-release github --yes
- run: yarn lerna publish from-git --yes

workflows:
Expand All @@ -97,7 +108,7 @@ workflows:
- unit-tests:
requires:
- prepare
- release:
- release-prod:
context: vault
requires:
- prepare
Expand All @@ -108,7 +119,14 @@ workflows:
branches:
only:
- main
- release-next:
context: vault
requires:
- prepare
- build
- unit-tests
- lint
filters:
branches:
only:
- next
- next-major
- alpha
- beta
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "independent",
"command": {
"version": {
"allowBranch": "main",
"allowBranch": ["main", "next"],
"conventionalCommits": true,
"message": "chore(release): updated release notes and package versions [ci skip]",
"ignoreChanges": [
Expand Down
12 changes: 12 additions & 0 deletions packages/experience-builder-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [3.0.1](https://github.com/contentful/experience-builder/compare/@contentful/experience-builder@3.0.0...@contentful/experience-builder@3.0.1) (2023-12-07)

**Note:** Version bump only for package @contentful/experience-builder

# [3.0.0](https://github.com/contentful/experience-builder/compare/@contentful/experience-builder@2.13.0...@contentful/experience-builder@3.0.0) (2023-12-06)

- feat!: register toolkit components and include wrap container by default [ALT-115] (#175) ([1097764](https://github.com/contentful/experience-builder/commit/1097764e33fa0a5a5b89007b04d0cf5f18d6d71e)), closes [#175](https://github.com/contentful/experience-builder/issues/175)

### BREAKING CHANGES

- Registered components will be wrapped with a div container by default. The `wrapComponent` option can be used when registering components to opt-out of this behavior if necessary.

# [2.13.0](https://github.com/contentful/experience-builder/compare/@contentful/experience-builder@2.12.0...@contentful/experience-builder@2.13.0) (2023-12-05)

### Features
Expand Down
11 changes: 8 additions & 3 deletions packages/experience-builder-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentful/experience-builder",
"version": "2.13.0",
"version": "3.0.1",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"typings": "./dist/src/index.d.ts",
Expand All @@ -9,7 +9,7 @@
"license": "MIT",
"scripts": {
"dev": "vite",
"prepare": "node ./bin/injectSDKVersion.cjs",
"prepare": "node ./bin/injectSDKVersion.cjs && relative-deps",
"prebuild": "node ./bin/injectSDKVersion.cjs",
"build": "vite build",
"watch": "vite build --watch",
Expand All @@ -25,7 +25,8 @@
"preview": "vite preview"
},
"dependencies": {
"@contentful/experience-builder-types": "^1.5.0",
"@contentful/experience-builder-components": "^0.0.1-alpha.11",
"@contentful/experience-builder-types": "^2.0.1",
"@contentful/rich-text-types": "^16.2.1",
"@contentful/visual-sdk": "^1.0.0-alpha.26",
"classnames": "^2.3.2",
Expand Down Expand Up @@ -59,6 +60,7 @@
"jsdom": "^21.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"relative-deps": "^1.0.7",
"rollup-plugin-peer-deps-external": "^2.2.4",
"semantic-release": "19.0.5",
"ts-jest": "^29.1.0",
Expand All @@ -67,6 +69,9 @@
"vite-plugin-dts": "^3.6.1",
"vite-plugin-svgr": "^4.1.0"
},
"relativeDependencies": {
"@contentful/experience-builder-components": "../../../experience-builder-toolkit/packages/components"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ describe('createDesignComponentRegistration', () => {

afterEach(() => {
registry.resetComponentRegistry();
jest.restoreAllMocks();
});

it('should return an existing component registration object if one already exists with the given definition ID', () => {
Expand Down
67 changes: 49 additions & 18 deletions packages/experience-builder-sdk/src/core/componentRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Components from '@contentful/experience-builder-components';
import { ComponentRegistration, ComponentDefinition } from '../types';
import {
OUTGOING_EVENTS,
Expand All @@ -10,6 +11,7 @@ import { builtInStyles as builtInStyleDefinitions } from './definitions/variable
import { ContentfulContainer } from '../components/ContentfulContainer';
import { containerDefinition } from './definitions/components';
import { sendMessage } from '../communication/sendMessage';
import { withComponentWrapper } from '../utils/withComponentWrapper';

const cloneObject = <T>(targetObject: T): T => {
if (typeof structuredClone !== 'undefined') {
Expand All @@ -19,21 +21,6 @@ const cloneObject = <T>(targetObject: T): T => {
return JSON.parse(JSON.stringify(targetObject));
};

const DEFAULT_COMPONENT_REGISTRATIONS = {
container: {
component: ContentfulContainer,
definition: containerDefinition,
},
} satisfies Record<string, ComponentRegistration>;

// pre-filling with the default component registrations
const componentRegistry = new Map<string, ComponentRegistration>([
[
DEFAULT_COMPONENT_REGISTRATIONS.container.definition.id,
DEFAULT_COMPONENT_REGISTRATIONS.container,
],
]);

const applyComponentDefinitionFallbacks = (componentDefinition: ComponentDefinition) => {
const clone = cloneObject(componentDefinition);
for (const variable of Object.values(clone.variables)) {
Expand Down Expand Up @@ -65,15 +52,59 @@ const applyBuiltInStyleDefinitions = (componentDefinition: ComponentDefinition)
export const enrichComponentDefinition = ({
component,
definition,
options,
}: ComponentRegistration): ComponentRegistration => {
const definitionWithFallbacks = applyComponentDefinitionFallbacks(definition);
const definitionWithBuiltInStyles = applyBuiltInStyleDefinitions(definitionWithFallbacks);
return {
component,
component: withComponentWrapper(component, options),
definition: definitionWithBuiltInStyles,
};
};

const DEFAULT_COMPONENT_REGISTRATIONS = {
container: {
component: ContentfulContainer,
definition: containerDefinition,
},
button: enrichComponentDefinition({
component: Components.Button,
definition: Components.ButtonComponentDefinition,
}),
heading: enrichComponentDefinition({
component: Components.Heading,
definition: Components.HeadingComponentDefinition,
}),
image: enrichComponentDefinition({
component: Components.Image,
definition: Components.ImageComponentDefinition,
}),
richText: enrichComponentDefinition({
component: Components.RichText,
definition: Components.RichTextComponentDefinition,
}),
text: enrichComponentDefinition({
component: Components.Text,
definition: Components.TextComponentDefinition,
}),
} satisfies Record<string, ComponentRegistration>;

// pre-filling with the default component registrations
const componentRegistry = new Map<string, ComponentRegistration>([
[
DEFAULT_COMPONENT_REGISTRATIONS.container.definition.id,
DEFAULT_COMPONENT_REGISTRATIONS.container,
],
[DEFAULT_COMPONENT_REGISTRATIONS.button.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.button],
[DEFAULT_COMPONENT_REGISTRATIONS.heading.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.heading],
[DEFAULT_COMPONENT_REGISTRATIONS.image.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.image],
[
DEFAULT_COMPONENT_REGISTRATIONS.richText.definition.id,
DEFAULT_COMPONENT_REGISTRATIONS.richText,
],
[DEFAULT_COMPONENT_REGISTRATIONS.text.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.text],
]);

export const sendRegisteredComponentsMessage = () => {
// Send the definitions (without components) via the connection message to the experience builder
const registeredDefinitions = Array.from(componentRegistry.values()).map(
Expand All @@ -99,10 +130,10 @@ export const sendConnectedEventWithRegisteredComponents = () => {

/**
* Registers multiple components and their component definitions at once
* @param componentRegistrations - Array<{ component: ReactElement, definition: ComponentDefinition }>
* @param componentRegistrations - ComponentRegistration[]
* @returns void
*/
export const defineComponents = (componentRegistrations: Array<ComponentRegistration>) => {
export const defineComponents = (componentRegistrations: ComponentRegistration[]) => {
for (const registration of componentRegistrations) {
// Fill definitions with fallbacks values
const enrichedComponentRegistration = enrichComponentDefinition(registration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export class EntityStore extends VisualSdkEntityStore {
entityLink: UnresolvedLink<'Entry' | 'Asset'>,
path: string[]
): string | undefined {
const entity = this.entitiesMap.get(entityLink.sys.id);
const entity =
entityLink.sys.linkType === 'Entry'
? this.entryMap.get(entityLink.sys.id)
: this.assetMap.get(entityLink.sys.id);

if (!entity || entity.sys.type !== entityLink.sys.linkType) {
console.warn(`Experience references unresolved entity: ${JSON.stringify(entityLink)}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from 'react';
import { withComponentWrapper } from './withComponentWrapper';
import { fireEvent, render } from '@testing-library/react';

const MyButton: React.FC<React.PropsWithChildren> = ({ children, ...props }) => (
<button {...props}>{children}</button>
);

describe('withComponentWrapper', () => {
describe('when component is wrapped', () => {
const WrappedButton = withComponentWrapper(MyButton);

it('events should be bound to the container div', () => {
const onClickSpy = jest.fn();
const onMouseDownSpy = jest.fn();
const onMouseUpSpy = jest.fn();

const { container } = render(
<WrappedButton onClick={onClickSpy} onMouseDown={onMouseDownSpy} onMouseUp={onMouseUpSpy}>
Click me
</WrappedButton>
);

fireEvent.click(container.firstChild!);
expect(onClickSpy).toHaveBeenCalledTimes(1);

fireEvent.mouseDown(container.firstChild!);
expect(onMouseDownSpy).toHaveBeenCalledTimes(1);

fireEvent.mouseUp(container.firstChild!);
expect(onMouseUpSpy).toHaveBeenCalledTimes(1);
});

it('extra props should be passed to the container div', () => {
const { container } = render(
<WrappedButton
data-cf-node-block-id="test1"
data-cf-node-block-type="test2"
data-cf-node-id="test3">
Click me
</WrappedButton>
);

expect(container.firstChild).toHaveAttribute('data-cf-node-block-id', 'test1');
expect(container.firstChild).toHaveAttribute('data-cf-node-block-type', 'test2');
expect(container.firstChild).toHaveAttribute('data-cf-node-id', 'test3');
});

it('can wrap a component with a custom tag', () => {
const WrappedButtonSpan = withComponentWrapper(MyButton, { wrapContainerTag: 'span' });

const { container } = render(
<WrappedButtonSpan className="my-span">Click me</WrappedButtonSpan>
);

expect(container.firstChild).toHaveClass('my-span');
expect(container.firstChild).toHaveTextContent('Click me');
});

it('classes get added to the correct elements', () => {
const { container, getByRole } = render(
<WrappedButton
data-cf-node-block-id="test1"
data-cf-node-block-type="test2"
data-cf-node-id="test3"
className="my-wrapper"
classes="my-class"
data-caca="yep">
Click me
</WrappedButton>
);

expect(container.firstChild).toHaveClass('my-wrapper');
expect(getByRole('button')).toHaveClass('my-class');
expect(getByRole('button')).toHaveAttribute('data-caca', 'yep');
});
});

describe('when component is not wrapped', () => {
const Button = withComponentWrapper(MyButton, { wrapComponent: false });

it('classes should be applied to the component itself', () => {
const { getByRole } = render(<Button classes="test">Click me</Button>);
expect(getByRole('button')).toHaveClass('test');
});

it('events should be bound to the component itself', () => {
const onClickSpy = jest.fn();
const onMouseDownSpy = jest.fn();
const onMouseUpSpy = jest.fn();

const { container } = render(
<Button onClick={onClickSpy} onMouseDown={onMouseDownSpy} onMouseUp={onMouseUpSpy}>
Click me
</Button>
);

fireEvent.click(container.firstChild!);
expect(onClickSpy).toHaveBeenCalledTimes(1);

fireEvent.mouseDown(container.firstChild!);
expect(onMouseDownSpy).toHaveBeenCalledTimes(1);

fireEvent.mouseUp(container.firstChild!);
expect(onMouseUpSpy).toHaveBeenCalledTimes(1);
});

it('extra props should be passed to the component', () => {
const { container } = render(
<Button
data-cf-node-block-id="test1"
data-cf-node-block-type="test2"
data-cf-node-id="test3">
Click me
</Button>
);
expect(container.firstChild).toHaveAttribute('data-cf-node-block-id', 'test1');
expect(container.firstChild).toHaveAttribute('data-cf-node-block-type', 'test2');
expect(container.firstChild).toHaveAttribute('data-cf-node-id', 'test3');
});

it('classes get added to the correct elements', () => {
const { container } = render(
<Button
data-cf-node-block-id="test1"
data-cf-node-block-type="test2"
data-cf-node-id="test3"
className="my-button" //so we can select it later
classes="my-class">
Click me
</Button>
);
expect(container.firstChild).toHaveClass('my-button');
expect(container.firstChild).toHaveClass('my-class');
});
});
});
Loading

0 comments on commit 9b6b5b3

Please sign in to comment.