Skip to content

fix[qwik]: ENG-7299 Default value not updating for custom components on ContentEditor #3947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
efe828f
feat(test-case): Test case for no default value custom component. WIP
Mar 4, 2025
76040a7
fix: force component key update
Mar 5, 2025
2c2db77
fix: Remove unnecessary changes
Mar 5, 2025
3c65c71
fix: merge conflict
Mar 6, 2025
72ae3ce
fix: made code qwik specific. Added condition to accept only react an…
Mar 6, 2025
02fc383
fix: lint
Mar 6, 2025
6d3fc1e
fix: removed unnecessary comment
Mar 7, 2025
9a5d339
fix(editing.spec.ts): Added a slight delay for page to get render and…
Mar 10, 2025
9da6401
fix: removed '.only' from test
Mar 10, 2025
d4a35e7
fix: instead of JSON to update key using counter to force update
Mar 11, 2025
12db0f9
fix: need mitosis update
Mar 11, 2025
3e92a26
fix: remove stringify from interactive element and using incremental …
Mar 12, 2025
a4310cf
fix: merge conflict
Mar 12, 2025
6b3dd9e
fix: mitosis used json instead of using replace code. replaced waitFo…
Mar 13, 2025
2808573
fix: removed logs and removed and hardcoded timeouts
Mar 17, 2025
c97f374
doc: changeset.md
Mar 17, 2025
ac7f88e
fix: instead of waiting expecting element to be visible
Mar 17, 2025
7474198
Merge branch 'main' into ENG-7299
Mar 17, 2025
d8ea63b
fix: remove detailElement wait
yash-builder Mar 18, 2025
3b64cc3
fix: lint
yash-builder Mar 18, 2025
0ba8ceb
fix: waiting for button to be enabled
yash-builder Mar 18, 2025
3478c53
👺
yash-builder Mar 18, 2025
f780509
fix: skipping test for accordion for qwik city
yash-builder Mar 27, 2025
126bcfc
Merge branch 'main' into ENG-7299
yash-builder Mar 28, 2025
68a7c14
Merge branch 'main' into ENG-7299
yash-builder Mar 28, 2025
7bb3f45
fix: 🏃
yash-builder Mar 28, 2025
27925d0
fix: updated text case
yash-builder Apr 1, 2025
6517f39
fix: .only removed from test case
yash-builder Apr 1, 2025
e133b60
Merge branch 'main' into ENG-7299
yash-builder Apr 1, 2025
5b585c4
merged main branch. testing flakey tests
yash-builder Apr 1, 2025
57c5eae
fix: skipping vue test for symbol list updating and attaching loom re…
yash-builder Apr 1, 2025
321641b
fix: updated description
yash-builder Apr 1, 2025
7620d48
fix: skipping nuxt flakey test
yash-builder Apr 1, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const LAST_COMPONENT_REGISTERED_MESSAGE =

test.describe('Custom components', () => {
test('correctly renders custom component', async ({ page, packageName, sdk }) => {
test.skip(!['angular', 'react'].includes(sdk));
test.skip(!['angular', 'react', 'qwik-city'].includes(sdk));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line doesn't have any impact, sdk can be qwik. packageNames are the different e2e/snippet server names and can be qwik-city

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh nice catch....I was testing somethings...I forgot to remove this

test.skip(
['react-sdk-next-14-app', 'react-sdk-next-15-app', 'remix', 'hydrogen'].includes(packageName)
);
Expand Down
21 changes: 19 additions & 2 deletions packages/sdks-tests/src/e2e-tests/editing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from '@playwright/test';

Check failure on line 1 in packages/sdks-tests/src/e2e-tests/editing.spec.ts

View workflow job for this annotation

GitHub Actions / Gen 2 SDKs (qwik-city)

[qwik-city] › editing.spec.ts:302:5 › Visual Editing › Accordion block › inserting a new detail item adds it to the correct place in the accordion

1) [qwik-city] › editing.spec.ts:302:5 › Visual Editing › Accordion block › inserting a new detail item adds it to the correct place in the accordion Test timeout of 30000ms exceeded.
import {
COLUMNS_WITH_NEW_SPACE,
COLUMNS_WITH_NEW_TEXT,
Expand All @@ -18,6 +18,7 @@
import { ADD_A_TEXT_BLOCK } from '../specs/duplicated-content-using-nested-symbols.js';
import { EDITING_STYLES } from '../specs/editing-styles.js';
import { ACCORDION_WITH_NO_DETAIL } from '../specs/accordion.js';
import { CUSTOM_COMPONENT_NO_DEFAULT_VALUE } from '../specs/custom-component-no-default-value.js';
import { NEW_BLOCK_ADD, NEW_BLOCK_ADD_2 } from '../specs/new-block-add.js';

const editorTests = ({
Expand Down Expand Up @@ -151,6 +152,16 @@
}
});

test('correctly updating custom components when default value is not set', async ({ page, basePort, sdk, packageName }) => {
test.skip(!['react', 'qwik-city'].includes(packageName));
await launchEmbedderAndWaitForSdk({ path: '/custom-components-no-default-value', basePort, page, sdk });
const newContent = cloneContent(CUSTOM_COMPONENT_NO_DEFAULT_VALUE);
newContent.data.blocks[0].component.options.text = "Hello";
await sendContentUpdateMessage({ page, newContent, model: 'page' });
const helloWorldText = page.frameLocator('iframe').locator('[builder-id="builder-d01784d1ca964e0da958ab4a4a891b08"]')
await expect(helloWorldText).toHaveText('Hello');
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by default it should render Hello right? because you're passing it as defaultValue? without needing to send the content update message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get you...🤔
I am answering this questions based on my understanding. Yes, by default it should render hello. defaultValue has not been passed yet if you see CUSTOM_COMPONENT_NO_DEFAULT_VALUE the object will look like this

component: {
    name: 'Description',
    options: {},
}

we will be requiring sendContentUpdateMessage to update the text message from "" to "Hello"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this: https://github.com/BuilderIO/builder/pull/3947/files#diff-f17fc2e9e6719ba501c3ab4df9c1fef742dc2c5dd3f709c622f6c79cdffcc1bdR99 add the defaultValue? i'm confused because on initial render if there is no initial value provided and we are providing a default value - we should be fallbacking to that

Copy link
Contributor

@samijaber samijaber Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sidmohanty11 your confusion stems from the fact that default values are only used when the block is created in the visual editor to determine the initial value for inputs: they are not used by the SDK at run-time.

@yash-builder one way to clear up such confusion is to avoid using the exact same values for different things. Have a defaultValue: 'FOO' and update the text here to Hello instead of having the exact same string Hello in both cases.

Also, as a general rule, when we add a test that involves editing content, i think it's best that it looks like this:

  • load initial content
  • check the initial value of a block
  • make an update to that block
  • check the value of the same block, and see that it changed as expected

In this test you're not checking the initial state of the block. You could add a toBeEmpty() check:

    test.skip(!['react', 'qwik-city'].includes(packageName));
    await launchEmbedderAndWaitForSdk({ path: '/custom-components-no-default-value', basePort, page, sdk });

    // check initial value here
    const helloWorldText = page.frameLocator('iframe').locator('[builder-id="builder-d01784d1ca964e0da958ab4a4a891b08"]')
    await expect(helloWorldText).toBeEmpty();

    const newContent = cloneContent(CUSTOM_COMPONENT_NO_DEFAULT_VALUE);
    newContent.data.blocks[0].component.options.text = "Hello";
    await sendContentUpdateMessage({ page, newContent, model: 'page' });
    const helloWorldText = page.frameLocator('iframe').locator('[builder-id="builder-d01784d1ca964e0da958ab4a4a891b08"]')
    await expect(helloWorldText).toHaveText('Hello');

Not a huge deal though, more of a general rule of thumb for these types of tests


test('removal of styles should work properly', async ({ page, packageName, sdk, basePort }) => {
test.skip(packageName === 'nextjs-sdk-next-app' || checkIsGen1React(sdk));

Expand Down Expand Up @@ -332,14 +343,20 @@
model: 'page',
});

await item1.click();
await expect(
page.frameLocator('iframe').getByText('Item 1')
).toBeVisible({ timeout: 5000 });

// Re-query the item1 element as it might have been recreated
const updatedItem1 = page.frameLocator('iframe').getByText('Item 1');
await updatedItem1.click();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we want to avoid hardcoded timeouts unless there's a special reason for them. Playwright has automated waiting and retrying so that we don't need to provide hardcoded timeout values all the time. Also, in this case, the default is already 5000 so this does nothing:

timeout: testType === 'snippet' ? 30000 : 5000,

you also don't need to redefine the locator twice:

Suggested change
await expect(
page.frameLocator('iframe').getByText('Item 1')
).toBeVisible({ timeout: 5000 });
// Re-query the item1 element as it might have been recreated
const updatedItem1 = page.frameLocator('iframe').getByText('Item 1');
await updatedItem1.click();
// Re-query the item1 element as it might have been recreated
const updatedItem1 = page.frameLocator('iframe').getByText('Item 1');
await expect(updatedItem1).toBeVisible();
await updatedItem1.click();


const detailElement = page.frameLocator('iframe').getByText(NEW_DETAILS_TEXT);
await detailElement.waitFor();

const [titleBox, detailBox] = await Promise.all([
item1.boundingBox(),
updatedItem1.boundingBox(),
detailElement.boundingBox(),

Check failure on line 359 in packages/sdks-tests/src/e2e-tests/editing.spec.ts

View workflow job for this annotation

GitHub Actions / Gen 2 SDKs (qwik-city)

[qwik-city] › editing.spec.ts:302:5 › Visual Editing › Accordion block › inserting a new detail item adds it to the correct place in the accordion

1) [qwik-city] › editing.spec.ts:302:5 › Visual Editing › Accordion block › inserting a new detail item adds it to the correct place in the accordion Error: locator.boundingBox: Test timeout of 30000ms exceeded. Call log: - waiting for locator('iframe').contentFrame().getByText('new detail') 357 | const [titleBox, detailBox] = await Promise.all([ 358 | updatedItem1.boundingBox(), > 359 | detailElement.boundingBox(), | ^ 360 | ]); 361 | 362 | if (!titleBox || !detailBox) { at /home/runner/work/builder/builder/packages/sdks-tests/src/e2e-tests/editing.spec.ts:359:23
]);

if (!titleBox || !detailBox) {
Expand Down Expand Up @@ -549,7 +566,7 @@
expect(endTextBlockBox).toBeDefined();

if (!middleTextBlockBox || !topTextBlockBox || !endTextBlockBox) {
throw new Error('New text block or text block not found');

Check failure on line 569 in packages/sdks-tests/src/e2e-tests/editing.spec.ts

View workflow job for this annotation

GitHub Actions / Gen 2 SDKs (qwik-city)

[qwik-city] › editing.spec.ts:528:5 › Visual Editing › New Block addition and deletion › should add new block in the middle

2) [qwik-city] › editing.spec.ts:528:5 › Visual Editing › New Block addition and deletion › should add new block in the middle Error: New text block or text block not found 567 | 568 | if (!middleTextBlockBox || !topTextBlockBox || !endTextBlockBox) { > 569 | throw new Error('New text block or text block not found'); | ^ 570 | } 571 | 572 | expect(middleTextBlockBox.y).toBeGreaterThan(topTextBlockBox.y); at /home/runner/work/builder/builder/packages/sdks-tests/src/e2e-tests/editing.spec.ts:569:15
}

expect(middleTextBlockBox.y).toBeGreaterThan(topTextBlockBox.y);
Expand Down
27 changes: 27 additions & 0 deletions packages/sdks-tests/src/specs/custom-component-no-default-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const CUSTOM_COMPONENT_NO_DEFAULT_VALUE = {
data: {
title: 'dynamic-button-sdk-test',
themeId: false,
blocks: [
{
'@type': '@builder.io/sdk:Element',
'@version': 2,
id: 'builder-d01784d1ca964e0da958ab4a4a891b08',
component: {
name: 'Description',
options: {},
},
responsiveStyles: {
large: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
flexShrink: '0',
boxSizing: 'border-box',
marginTop: '20px',
},
},
},
],
},
};
2 changes: 2 additions & 0 deletions packages/sdks-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { VIDEO_LAZY_LOAD } from './video-lazy-load.js';
import { COLUMNS_VERTICAL_CENTER_FLEX } from './columns-vertical-center-flex.js';
import { DYNAMIC_ELEMENT } from './dynamic-element.js';
import { CUSTOM_CODE_DOM_UPDATE } from './custom-code-dom-update.js';
import { CUSTOM_COMPONENT_NO_DEFAULT_VALUE } from './custom-component-no-default-value.js';
import { NEW_BLOCK_ADD } from './new-block-add.js';

import { DYNAMIC_BUTTON } from './dynamic-button.js';
Expand Down Expand Up @@ -196,6 +197,7 @@ export const PAGES: Record<string, Page> = {
'/accordion-no-detail': { content: ACCORDION_WITH_NO_DETAIL },
'/symbol-tracking': { content: SYMBOL_TRACKING },
'/columns-with-different-widths': { content: COLUMNS_WITH_DIFFERENT_WIDTHS },
'/custom-components-no-default-value': { content: CUSTOM_COMPONENT_NO_DEFAULT_VALUE },
'/custom-components-models-show': {
content: CUSTOM_COMPONENTS_MODELS_RESTRICTION,
target: ['react'],
Expand Down
9 changes: 9 additions & 0 deletions packages/sdks/e2e/qwik-city/src/components/Description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { component$ } from '@builder.io/qwik';

export const Description = component$((props: { text: string }) => {
return (
<div>
<h3>{props.text}</h3>
</div>
);
});
7 changes: 7 additions & 0 deletions packages/sdks/e2e/qwik-city/src/components/Hello.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { component$ } from '@builder.io/qwik';

export const Hello = component$(() => {
return (
<div>hello</div>
);
});
25 changes: 24 additions & 1 deletion packages/sdks/e2e/qwik-city/src/routes/[...index]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { routeLoader$ } from '@builder.io/qwik-city';
import { Content, _processContentResult } from '@builder.io/sdk-qwik';
import { getProps } from '@sdk/tests';
import BuilderBlockWithClassName from '~/components/BuilderBlockWithClassName';
import { Description } from '~/components/Description';
import { Hello } from '~/components/Hello';

const builderBlockWithClassNameCustomComponent = {
name: 'BuilderBlockWithClassName',
Expand Down Expand Up @@ -46,6 +48,25 @@ const builderBlockWithClassNameCustomComponent = {
],
};

const CUSTOM_COMPONENT = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const CUSTOM_COMPONENT = [
const CUSTOM_COMPONENTS = [

{
name: 'Hello',
component: Hello,
inputs: [],
},
Comment on lines +53 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you added the Hello component for this test:https://github.com/sidmohanty11/builder/blob/general-support-vc/packages/sdks-tests/src/e2e-tests/custom-components.spec.ts#L11 right? you will also need to update the content of it from just hello to hello World

{
name: 'Description',
component: Description,
inputs: [
{
name: 'text',
type: 'string',
defaultValue: 'Hello',
},
],
},
];

export const useBuilderContentLoader = routeLoader$(async (event) => {
const data = await getProps({
pathname: event.url.pathname,
Expand All @@ -67,7 +88,9 @@ export default component$(() => {
{contentProps.value ? (
<Content
{...(contentProps.value as any)}
customComponents={[builderBlockWithClassNameCustomComponent]}
customComponents={[
...CUSTOM_COMPONENT,
builderBlockWithClassNameCustomComponent]}
/>
) : (
<div>Content Not Found</div>
Expand Down
12 changes: 12 additions & 0 deletions packages/sdks/e2e/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { builderBlockWithClassNameCustomComponent } from './components/BuilderBl
import ComponentNeedsHello from './components/ComponentNeedsHello';
import { componentWithLocalizedSubfieldsInfo } from './components/ComponentWithLocalizedSubfields';
import Hello from './components/Hello';
import { Description } from './components/Description';

const DataComp = (props: {
pathname: string;
Expand Down Expand Up @@ -88,6 +89,17 @@ function App() {
models: ['test-model'],
}),
},
{
name: 'Description',
component: Description,
inputs: [
{
name: 'text',
type: 'string',
defaultValue: 'Hello',
},
],
},
{
name: 'ComponentNeedsHello',
component: ComponentNeedsHello,
Expand Down
7 changes: 7 additions & 0 deletions packages/sdks/e2e/react/src/components/Description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const Description = (props: { text: string }) => {
return (
<div>
<h3>{props.text}</h3>
</div>
);
};
16 changes: 16 additions & 0 deletions packages/sdks/mitosis.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,21 @@ const QWIK_ONUPDATE_TO_USEVISIBLETASK = () => ({
},
});

const QWIK_FORCE_RENDER_COUNT_FOR_RENDERING_CUSTOM_COMPONENT_DEFAULT_VALUE = () => ({
json: {
post: (json) => {
if (json.name === 'InteractiveElement') {
console.log('json', JSON.stringify(json, null, 2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log('json', JSON.stringify(json, null, 2));

json.children[0].meta.else.bindings['key'] = {
code: "'wrapper-' + state.forceRenderCount",
bindingType: "expression",
type: "single"
};
}
},
},
});

/**
* @type {MitosisConfig}
*/
Expand Down Expand Up @@ -1239,6 +1254,7 @@ module.exports = {
},
},
}),
QWIK_FORCE_RENDER_COUNT_FOR_RENDERING_CUSTOM_COMPONENT_DEFAULT_VALUE,
QWIK_ONUPDATE_TO_USEVISIBLETASK,
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Show, useMetadata, useStore, type Signal } from '@builder.io/mitosis';
import {
Show,
onUpdate,
useMetadata,
useStore,
useTarget,
type Signal,
} from '@builder.io/mitosis';
import type { BuilderContextInterface } from '../../../context/types.js';
import { getBlockActions } from '../../../functions/get-block-actions.js';
import { getBlockProperties } from '../../../functions/get-block-properties.js';
Expand Down Expand Up @@ -32,6 +39,7 @@ useMetadata({
*/
export default function InteractiveElement(props: InteractiveElementProps) {
const state = useStore({
forceRenderCount: 0,
get attributes() {
return props.includeBlockProps
? {
Expand All @@ -51,6 +59,16 @@ export default function InteractiveElement(props: InteractiveElementProps) {
},
});

// Use onUpdate to track prop changes (Mitosis equivalent of useEffect/useTask)
onUpdate(() => {
useTarget({
qwik: () => {
state.forceRenderCount = state.forceRenderCount + 1;
},
default: () => {},
});
}, [props.wrapperProps, props.block?.component?.options]);

return (
<Show
when={props.Wrapper.load}
Expand Down
Loading