Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f57783b
feat(edit-content): integrate legacy image editor with binary field w…
nicobytes Jun 15, 2026
b77ccdd
Merge branch 'main' into 34420-epic-image-editor-feature-parity-migra…
nicobytes Jun 15, 2026
2a48c92
fix(dot-legacy-image-editor): restore dispatch spy in unit tests for …
nicobytes Jun 15, 2026
1cdb42e
feat(binary-field): add end-to-end tests for binary field image editor
nicobytes Jun 15, 2026
abd47c7
Merge branch 'main' into 34420-epic-image-editor-feature-parity-migra…
nicobytes Jun 15, 2026
9ceade3
refactor(dot-legacy-image-editor): enhance unit tests and message han…
nicobytes Jun 15, 2026
e7f221d
Merge branch '34420-epic-image-editor-feature-parity-migrate-to-angul…
nicobytes Jun 15, 2026
4d22eb4
refactor(binary-field-image-editor): update image URL and enhance ifr…
nicobytes Jun 15, 2026
e300be7
delete(binary-field-image-editor): remove outdated E2E specification …
nicobytes Jun 15, 2026
a7de084
refactor(binary-field): migrate binary field to new file field component
nicobytes Jun 15, 2026
6b8bdd9
refactor(image-editor): implement new dialog-based image editor launcher
nicobytes Jun 15, 2026
1722212
test(binary-field): enhance E2E tests for binary field image handling
nicobytes Jun 15, 2026
aca06bc
refactor(tests): restructure required field tests for binary, file, a…
nicobytes Jun 15, 2026
e0fa611
Merge branch 'main' into 34420-unify-component
nicobytes Jun 16, 2026
bc5deab
Merge origin/main into 34420-unify-component
Copilot Jun 16, 2026
19e23f1
Merge branch 'main' into 34420-unify-component
nicobytes Jun 16, 2026
a065cb9
fix(binary-field): enhance hover behavior and iframe visibility checks
nicobytes Jun 16, 2026
c0c5814
Merge branch 'main' into 34420-unify-component
nicobytes Jun 16, 2026
27b982e
refactor(binary-field): update AI button tooltip assertion and enhanc…
nicobytes Jun 16, 2026
732b70f
Merge branch '34420-unify-component' of github.com:dotCMS/core into 3…
nicobytes Jun 16, 2026
7509d1e
Merge branch 'main' into 34420-unify-component
nicobytes Jun 16, 2026
03fdeee
feat(e2e-tests): enhance Playwright rules and improve test reliability
nicobytes Jun 16, 2026
bb852d9
Merge branch '34420-unify-component' of github.com:dotCMS/core into 3…
nicobytes Jun 16, 2026
23fa63d
chore: update justfile and enhance Angular app module
nicobytes Jun 17, 2026
d7b9318
refactor(dot-file-field): update styles and structure for improved la…
nicobytes Jun 17, 2026
cb2f2c1
Merge branch 'main' into 34420-unify-component
nicobytes Jun 17, 2026
4204d01
refactor(dot-file-field): streamline component structure and enhance …
nicobytes Jun 17, 2026
3f5ddbf
refactor(dot-file-field): enhance file field preview component with d…
nicobytes Jun 17, 2026
396873c
refactor(agentic-tools): update spec.json and enhance Jest configuration
nicobytes Jun 17, 2026
baa2b30
Merge origin/main into 34420-unify-component
Copilot Jun 17, 2026
f152460
Merge branch 'main' into 34420-unify-component
nicobytes Jun 18, 2026
ab52b9b
fix(dot-file-field-preview): adjust z-index for improved layering in …
adrianjm-dotCMS Jun 18, 2026
a8765c0
refactor(dot-edit-content-file-field): update value handling and enha…
adrianjm-dotCMS Jun 22, 2026
3a2f274
Merge branch 'main' into 34420-unify-component
adrianjm-dotCMS Jun 22, 2026
1a4d762
merge main
adrianjm-dotCMS Jun 22, 2026
5808e0e
Merge branch '34420-unify-component' of https://github.com/dotCMS/cor…
adrianjm-dotCMS Jun 22, 2026
cc3433e
style(dot-file-field-preview): fix prettier formatting on template
adrianjm-dotCMS Jun 22, 2026
1a5d538
fix(e2e): fix flaky content-search query modal tests
adrianjm-dotCMS Jun 23, 2026
1f86c4d
fix(tests): enhance portlet integrity tests for query modal and API link
adrianjm-dotCMS Jun 23, 2026
6a1f380
fix(e2e): fix flaky content-search query modal tests
adrianjm-dotCMS Jun 23, 2026
159d54d
fix(dot-file-field): improve value comparison logic in file field com…
adrianjm-dotCMS Jun 23, 2026
7f885f7
style(dot-file-field): update styles to use CSS variables for consist…
adrianjm-dotCMS Jun 23, 2026
3cefdcb
Merge branch 'main' into 34420-unify-component
adrianjm-dotCMS Jun 25, 2026
4945389
refactor(dot-form-import-url): streamline loading state handling in i…
adrianjm-dotCMS Jun 25, 2026
2a6c04c
Merge branch '34420-unify-component' of https://github.com/dotCMS/cor…
adrianjm-dotCMS Jun 25, 2026
62ce252
Enhance DotFileFieldComponent to support system options overrides
adrianjm-dotCMS Jun 25, 2026
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
48 changes: 33 additions & 15 deletions core-web/apps/dotcms-binary-field-builder/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,57 @@
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { firstValueFrom } from 'rxjs';

import { HttpClientModule } from '@angular/common/http';
import { DoBootstrap, Injector, NgModule, Type } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import {
DoBootstrap,
inject,
Injector,
NgModule,
provideAppInitializer,
Type
} from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideAnimations } from '@angular/platform-browser/animations';

import { DotMessageService, DotUploadService } from '@dotcms/data-access';
import { DotEditContentBinaryFieldComponent } from '@dotcms/edit-content';
import {
DotMessageService,
DotUploadService,
DotWorkflowActionsFireService
} from '@dotcms/data-access';
import { DotBinaryFieldCeBridgeComponent } from '@dotcms/edit-content';
import { provideDotCMSTheme } from '@dotcms/ui';

import { AppComponent } from './app.component';

interface ContenttypeFieldElement {
tag: string;
component: Type<DotEditContentBinaryFieldComponent>;
component: Type<DotBinaryFieldCeBridgeComponent>;
}

const CONTENTTYPE_FIELDS: ContenttypeFieldElement[] = [
{
tag: 'dotcms-binary-field',
component: DotEditContentBinaryFieldComponent
component: DotBinaryFieldCeBridgeComponent
}
];

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
DotEditContentBinaryFieldComponent,
MonacoEditorModule
],
providers: [DotMessageService, DotUploadService, provideDotCMSTheme()]
imports: [BrowserModule, DotBinaryFieldCeBridgeComponent, MonacoEditorModule],
providers: [
provideHttpClient(),
provideAnimations(),
DotMessageService,
DotUploadService,
DotWorkflowActionsFireService,
provideDotCMSTheme(),
provideAppInitializer(() => {
const dotMessageService = inject(DotMessageService);

return firstValueFrom(dotMessageService.init());
})
]
})
export class AppModule implements DoBootstrap {
constructor(private readonly injector: Injector) {}
Expand Down
7 changes: 6 additions & 1 deletion core-web/apps/dotcms-ui-e2e/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"extends": ["plugin:playwright/recommended", "../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*", "playwright-report/**", "test-results/**", "target/**"],
"rules": {
"playwright/expect-expect": "off"
"playwright/expect-expect": "off",
"playwright/no-wait-for-timeout": "error",
"playwright/no-force-option": "error",
"playwright/no-skipped-test": "error",
"playwright/no-conditional-in-test": "error",
"playwright/prefer-locator": "error"
},
"overrides": [
{
Expand Down
4 changes: 2 additions & 2 deletions core-web/apps/dotcms-ui-e2e/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ playwright.config.ts
```bash
pnpm nx e2e dotcms-ui-e2e --grep "pattern"
HEADLESS=true pnpm nx e2e dotcms-ui-e2e --grep "pattern"
pnpm e2e:dev | e2e:dev:headless | e2e:ci | e2e:ui # from dotcms-ui-e2e/
pnpm e2e:dev | e2e:dev:headless | e2e:ci | e2e:ui # from core-web/
npx playwright codegen http://localhost:4200/dotAdmin
```

Expand All @@ -48,7 +48,7 @@ Unsure? **Codegen first.** Avoid fragile `#dijit_*` IDs, CSS when `data-testid`
- **Angular:** shell, edit form, dialogs — main `page`.
- **Dojo:** content listing in `#detailFrame` — `getLegacyFrame(page)` from `@utils/iframe`.
- **Nav:** `Portlet` from `@utils/portlets`; new content via `NewEditContentFormPage.goToNew()` (listing → New), not direct `/content/new/` URL.
- **Dojo menus:** `click({ force: true })` after `waitFor({ state: 'visible' })`.
- **Dojo menus:** wait for menu visibility, then click normally (no `force: true`).

Field-specific selectors and flows: copy `tests/.../helpers/` and nearest `*.spec.ts` (e.g. `relationship-field/`).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { expect, type Frame, type Page } from '@playwright/test';
import { clickAddNewContentFromList, goToContentList } from '@utils/contentListingNavigation';

const LEGACY_EDIT_FRAME_URL_PATTERN = /edit_contentlet|portlet\/ext\/contentlet/;
const LEGACY_EDIT_FRAME_URL_PATTERN = /edit_contentlet/i;

export class LegacyEditContentFormPage {
constructor(private page: Page) {}

/**
* Returns the legacy content edit iframe (edit_contentlet JSP), distinct from the shell detailFrame.
* Returns the legacy content edit iframe (edit_contentlet JSP) inside the PrimeNG dialog,
* not the Dojo listing frame (view_contentlets).
*/
async getLegacyContentFrame(): Promise<Frame> {
let frame: Frame | undefined;

await expect
.poll(
() => {
frame = this.page
const matchingFrames = this.page
.frames()
.find((f) => LEGACY_EDIT_FRAME_URL_PATTERN.test(f.url()));
.filter((f) => LEGACY_EDIT_FRAME_URL_PATTERN.test(f.url()));

frame = matchingFrames.at(-1);
return frame;
},
{ timeout: 20000 }
Expand All @@ -31,12 +35,11 @@ export class LegacyEditContentFormPage {
}

/**
* Navigates to create new content in the legacy editor (Dojo portlet inside detailFrame).
* URL: /dotAdmin/#/c/content/new/{contentTypeVariable}
* Navigates to create new content in the legacy editor (Dojo listing → Add New Content).
*/
async goToLegacyNew(contentTypeVariable: string) {
await this.page.goto(`/dotAdmin/#/c/content/new/${contentTypeVariable}`);
await this.page.waitForLoadState('domcontentloaded');
await goToContentList(this.page, contentTypeVariable);
await clickAddNewContentFromList(this.page);

const frame = await this.getLegacyContentFrame();
await frame.locator('dotcms-binary-field').first().waitFor({
Expand Down
26 changes: 3 additions & 23 deletions core-web/apps/dotcms-ui-e2e/src/pages/newEditContentForm.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect, Page } from '@playwright/test';
import { getLegacyFrame } from '@utils/iframe';
import { Portlet } from '@utils/portlets';
import { clickAddNewContentFromList, goToContentList } from '@utils/contentListingNavigation';

export class NewEditContentFormPage {
constructor(private page: Page) {}
Expand All @@ -10,34 +9,15 @@ export class NewEditContentFormPage {
* URL: /dotAdmin/#/c/content?filter=ContentTypeName
*/
async goToContentList(contentTypeVariable: string) {
await this.page.goto(`${Portlet.Content}?filter=${contentTypeVariable}`);
await this.page.waitForLoadState('domcontentloaded');
await goToContentList(this.page, contentTypeVariable);
}

/**
* From the content listing (Dojo portlet inside iframe), clicks the "+"
* dropdown and selects "Add New Content" to open the new content form.
*/
async clickNewContentFromList() {
const frame = getLegacyFrame(this.page);

// Wait for the Dojo iframe to fully load and widgets to initialize
await frame
.locator('.dijitDropDownButton')
.first()
.waitFor({ state: 'visible', timeout: 15000 });
// Small delay for Dojo widget initialization after DOM is visible
await this.page.waitForTimeout(500);

// Click the Dojo "+" dropdown button
const addButton = frame.locator('.dijitDropDownButton [role="button"]').first();
await addButton.click();

// The dropdown menu renders inside the iframe.
// Use force:true because Dojo menus can flicker during animation.
const addNewOption = frame.locator('.dijitMenuItemLabel', { hasText: 'Add New Content' });
await addNewOption.waitFor({ state: 'visible', timeout: 10000 });
await addNewOption.click({ force: true });
await clickAddNewContentFromList(this.page);

// Wait for the Angular form to render (replaces networkidle which is unreliable in SPAs)
await this.page.getByTestId('title').waitFor({ state: 'visible', timeout: 15000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,38 @@ export class ContentListingHelper {

/**
* Clicks the "createOptions" gear button and then "Show Query".
* Waits for #queryResults to confirm the modal fully rendered.
*
* The "Show Query" menu item lives in a Dojo popup that is always in the DOM
* but hidden until the button is clicked. In CI the popup can take longer to
* transition to visible, so we allow up to 15 s before giving up.
*/
async openQueryModal() {
const optionsBtn = this.frame.getByRole('button', { name: 'createOptions' });
await optionsBtn.waitFor({ state: 'visible', timeout: 10000 });
await optionsBtn.click();

const showQueryLink = this.frame.getByText('Show Query');
await showQueryLink.waitFor({ state: 'visible', timeout: 5000 });
await showQueryLink.waitFor({ state: 'visible', timeout: 15000 });
await showQueryLink.click();

await this.frame.locator('#queryResults').waitFor({ state: 'visible', timeout: 15000 });
}

/**
* Opens the add-content dropdown (Dojo). Use `force: true` to handle animation flicker.
* Opens the add-content dropdown (Dojo).
*/
async openAddContentDropdown() {
await this.addDropdownButton.waitFor({ state: 'visible', timeout: 10000 });
await this.addDropdownButton.click();
}

/**
* Returns the "API" span element inside the query modal.
* The element is a <span class="dot-api-link"> — not an <a> tag — and calls
* window.open() via an async AJAX POST when clicked.
*/
get queryModalApiLink() {
return this.frame.locator('.dot-api-link');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,30 @@ test.describe('Content listing portlet — UI integrity', () => {
await listing.goto();
await listing.openQueryModal();

// openQueryModal already waits for #queryResults, this just confirms it
await expect(listing.frame.locator('#queryResults')).toBeVisible();
});

test.skip('API link in query modal opens new tab', async ({ page }) => {
test('API link in query modal opens new tab @smoke', async ({ page }) => {
const listing = new ContentListingHelper(page);
await listing.goto();
await listing.openQueryModal();

const newTabPromise = page.waitForEvent('popup');
await listing.frame.getByText('API', { exact: true }).click();
const newTab = await newTabPromise;
// The "API" span calls window.open() inside a Dojo XHR callback — browsers block
// that as a popup because it is no longer a direct user gesture. Intercept the
// POST so it resolves instantly, keeping window.open() close enough to the click
// for Playwright (popup blocking disabled) to capture it reliably.
await page.route('**/api/content/_search', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ contentlets: [], total: 0 })
})
);

const newPagePromise = page.context().waitForEvent('page');
await listing.queryModalApiLink.click();
const newTab = await newPagePromise;

await newTab.waitForLoadState();
expect(newTab.url()).toBeTruthy();
Expand Down
Loading
Loading