Skip to content
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

fix[dynamic-renderer]: ENG-8440 Button links cannot be used in Angular Gen 2 SDK #3937

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/loud-jars-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@builder.io/sdk-angular": patch
---

Resolved assertion error encountered when dynamically switching components
Copy link
Contributor

Choose a reason for hiding this comment

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

This changeset is too low-level. I prefer changesets to focus on what customer-facing impact they have. In this case, the fact that Visual Editing could cause a crash:

Suggested change
Resolved assertion error encountered when dynamically switching components
Fix: crashes when visually editing blocks (encountered when SDK dynamically switched HTML elements)

70 changes: 70 additions & 0 deletions packages/sdks-tests/src/e2e-tests/dynamic-button.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect } from '@playwright/test';
import { checkIsRN, test } from '../helpers/index.js';
import {
cloneContent,
launchEmbedderAndWaitForSdk,
sendPatchOrUpdateMessage,
} from '../helpers/visual-editor.js';
import { DYNAMIC_BUTTON } from '../specs/dynamic-button.js';

test.describe('Dynamic Button', () => {
test('should render a button', async ({ page, sdk, basePort, packageName }) => {

test.fail(sdk === 'svelte' || sdk === 'oldReact', 'Not showing the href attribute in Svelte');
test.skip(
packageName === 'nextjs-sdk-next-app' ||
packageName === 'gen1-next14-pages' ||
packageName === 'gen1-next15-app' ||
packageName === 'gen1-remix'
);
await launchEmbedderAndWaitForSdk({
path: '/dynamic-button',
basePort,
page,
sdk,
});

const buttonLocator = checkIsRN(sdk) ? page.frameLocator('iframe').locator('button') : page
.frameLocator('iframe')
.locator('[builder-id="builder-b53d1cc2bcbb481b869207fdd97ee1db"]');

await expect(buttonLocator).toHaveText('Click me!');
const newContent = cloneContent(DYNAMIC_BUTTON);

// simulating typing in the link field
await sendPatchOrUpdateMessage({
page,
content: cloneContent(DYNAMIC_BUTTON),
model: 'page',
sdk,
updateFn: () => '#',
path: '/data/blocks/0/component/options/link',
});

await sendPatchOrUpdateMessage({
page,
content: newContent,
model: 'page',
sdk,
updateFn: () => '#g',
path: '/data/blocks/0/component/options/link',
});

await sendPatchOrUpdateMessage({
page,
content: newContent,
model: 'page',
sdk,
updateFn: () => '#go',
path: '/data/blocks/0/component/options/link',
});

const updatedButtonLocator = checkIsRN(sdk) ? page.frameLocator('iframe').locator('a') : page
.frameLocator('iframe')
.locator('[builder-id="builder-b53d1cc2bcbb481b869207fdd97ee1db"]');

await expect(updatedButtonLocator).toBeVisible();
Copy link
Contributor

Choose a reason for hiding this comment

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

also can you check the href value?


await expect(updatedButtonLocator).toHaveAttribute('href', '#go');
});
});
40 changes: 40 additions & 0 deletions packages/sdks-tests/src/specs/dynamic-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const DYNAMIC_BUTTON = {
data: {
title: 'dynamic-button-sdk-test',
themeId: false,
blocks: [
{
'@type': '@builder.io/sdk:Element',
'@version': 2,
id: 'builder-b53d1cc2bcbb481b869207fdd97ee1db',
component: {
name: 'Core:Button',
options: {
text: 'Click me!',
openLinkInNewTab: false,
},
},
responsiveStyles: {
large: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
flexShrink: '0',
boxSizing: 'border-box',
marginTop: '20px',
appearance: 'none',
paddingTop: '15px',
paddingBottom: '15px',
paddingLeft: '25px',
paddingRight: '25px',
backgroundColor: 'black',
color: 'white',
borderRadius: '4px',
textAlign: 'center',
cursor: 'pointer',
},
},
},
],
},
};
3 changes: 2 additions & 1 deletion packages/sdks-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +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 { DYNAMIC_BUTTON } from './dynamic-button.js';
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
Expand Down Expand Up @@ -279,6 +279,7 @@ export const PAGES: Record<string, Page> = {
'/can-track-false-pre-init': { content: HOMEPAGE, target: 'gen1' },
'/dynamic-element': { content: DYNAMIC_ELEMENT },
'/custom-code-dom-update': { content: CUSTOM_CODE_DOM_UPDATE },
'/dynamic-button': { content: DYNAMIC_BUTTON },
} as const;

export type Path = keyof typeof PAGES;
Expand Down
22 changes: 15 additions & 7 deletions packages/sdks/output/angular/scripts/generate-dynamic-renderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const dynamicComponentTemplate = (tagName) => {
export class Dynamic${capitalize(tagName)} {
@Input() attributes!: any;
@Input() actionAttributes?: any;
@Input() tagName?: string;
@ViewChild('v', { read: ElementRef }) v!: ElementRef;
_listenerFns = new Map<string, () => void>();
constructor(private renderer: Renderer2) {}
Expand Down Expand Up @@ -121,7 +122,7 @@ const generateComponents = () => {
<ng-container *ngIf="useTypeOf(TagName) === 'string'">
<ng-container
*ngComponentOutlet="
TagName;
getComponentType(TagName);
inputs: {
attributes: attributes,
actionAttributes: actionAttributes,
Expand Down Expand Up @@ -196,14 +197,21 @@ export default class DynamicRenderer {

constructor(private vcRef: ViewContainerRef) {}

private tagComponentMap: { [key: string]: any } = {
${htmlElements.map((el) => `'${el}': Dynamic${capitalize(el)}`).join(',\n ')}
Copy link
Contributor

Choose a reason for hiding this comment

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

also can you check the dynamic-a for this part of the jira:

Additionally, if you use anchor tags by setting the link to #stuff and place a block with html attribute of id = stuff it will open a new page by appending #stuff to the url rather than focusing on the html element named stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I missed this part. Thank you for adding it! Let me check it

};

getComponentType(tagName: string): any {
return this.tagComponentMap[tagName] || null;
}

ngOnInit() {
if (typeof this.TagName === 'string') {
switch (this.TagName) {
${htmlElements.map((el) => `case '${el}': this.TagName = Dynamic${capitalize(el)}; break;`).join('\n ')}
default:
this.tagName = this.TagName;
this.TagName = DynamicElement;
break;
if (this.tagComponentMap[this.TagName]) {
this.TagName = this.tagComponentMap[this.TagName];
} else {
Comment on lines +210 to +212
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand it correctly, map lookups are faster than switch case statements? and is that why angular was unable to createComponent quickly when we type? 🤯

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yess!!

this.tagName = this.TagName;
this.TagName = DynamicElement;
}
}
this.myContent = [this.vcRef.createEmbeddedView(this.tagnameTemplateRef).rootNodes];
Expand Down
Loading