Skip to content

Commit

Permalink
Merge branch 'main' into cb/karma-webdriverio-replace
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-bromann authored Mar 12, 2024
2 parents 13700f4 + 41f877e commit 6d0e6c3
Show file tree
Hide file tree
Showing 16 changed files with 224 additions and 7 deletions.
3 changes: 2 additions & 1 deletion src/compiler/config/load-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const loadConfig = async (init: LoadConfigInit = {}): Promise<LoadConfigR
configPath = loadedConfigFile.configPath;
unknownConfig.config = { ...loadedConfigFile, ...config };
unknownConfig.config.configPath = configPath;
unknownConfig.config.rootDir = normalizePath(dirname(configPath));
unknownConfig.config.rootDir =
typeof config.rootDir === 'string' ? config.rootDir : normalizePath(dirname(configPath));
} else {
// no stencil.config.ts or .js file, which is fine
unknownConfig.config = { ...config };
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/config/test/load-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('load config', () => {
sys,
config: {
hashedFileNameLength: 9,
rootDir: '/foo/bar',
},
initTsConfig: true,
});
Expand All @@ -50,6 +51,8 @@ describe('load config', () => {
expect<ConfigFlags>(actualConfig.flags).toEqual({ dev: true });
expect(actualConfig.extras).toBeDefined();
expect(actualConfig.extras!.enableImportInjection).toBe(true);
// respects custom root dir
expect(actualConfig.rootDir).toBe('/foo/bar');
});

it('uses the provided config path when no initial config provided', async () => {
Expand Down
28 changes: 28 additions & 0 deletions src/compiler/config/test/validate-dev-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,32 @@ describe('validateDevServer', () => {
const { config } = validateConfig(inputConfig, mockLoadConfigInit());
expect(config.devServer.prerenderConfig).toBe(wwwOutputTarget.prerenderConfig);
});

describe('pingRoute', () => {
it('should default to /ping', () => {
// Ensure the pingRoute is not set in the inputConfig so we know we're testing the
// default value added during validation
delete inputConfig.devServer.pingRoute;
const { config } = validateConfig(inputConfig, mockLoadConfigInit());
expect(config.devServer.pingRoute).toBe('/ping');
});

it('should set user defined pingRoute', () => {
inputConfig.devServer = { ...inputDevServerConfig, pingRoute: '/my-ping' };
const { config } = validateConfig(inputConfig, mockLoadConfigInit());
expect(config.devServer.pingRoute).toBe('/my-ping');
});

it('should prefix pingRoute with a "/"', () => {
inputConfig.devServer = { ...inputDevServerConfig, pingRoute: 'my-ping' };
const { config } = validateConfig(inputConfig, mockLoadConfigInit());
expect(config.devServer.pingRoute).toBe('/my-ping');
});

it('should clear ping route if set to null', () => {
inputConfig.devServer = { ...inputDevServerConfig, pingRoute: null };
const { config } = validateConfig(inputConfig, mockLoadConfigInit());
expect(config.devServer.pingRoute).toBe(null);
});
});
});
10 changes: 10 additions & 0 deletions src/compiler/config/validate-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export const validateDevServer = (config: d.ValidatedConfig, diagnostics: d.Diag

devServer.address = devServer.address.split('/')[0];

// Validate "ping" route option
if (devServer.pingRoute !== null) {
let pingRoute = isString(devServer.pingRoute) ? devServer.pingRoute : '/ping';
if (!pingRoute.startsWith('/')) {
pingRoute = `/${pingRoute}`;
}

devServer.pingRoute = pingRoute;
}

// split on `:` to get the domain and the (possibly present) port
// separately. we've already sliced off the protocol (if present) above
// so we can safely split on `:` here.
Expand Down
6 changes: 6 additions & 0 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,12 @@ export interface RenderNode extends HostElement {
*/
host?: Element;

/**
* On Ref Function:
* Callback function to be called when the slotted node ref is ready.
*/
['s-rf']?: (elm: Element) => unknown;

/**
* Is initially hidden
* Whether this node was originally rendered with the `hidden` attribute.
Expand Down
9 changes: 9 additions & 0 deletions src/declarations/stencil-public-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,15 @@ export interface DevServerConfig extends StencilDevServerConfig {
prerenderConfig?: string;
protocol?: 'http' | 'https';
srcIndexHtml?: string;

/**
* Route to be used for the "ping" sub-route of the Stencil dev server.
* This route will return a 200 status code once the Stencil build has finished.
* Setting this to `null` will disable the ping route.
*
* Defaults to `/ping`
*/
pingRoute?: string | null;
}

export interface HistoryApiFallback {
Expand Down
15 changes: 15 additions & 0 deletions src/dev-server/request-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ export function createRequestHandler(devServerConfig: d.DevServerConfig, serverC
return serverCtx.serve302(req, res);
}

if (devServerConfig.pingRoute !== null && req.pathname === devServerConfig.pingRoute) {
return serverCtx
.getBuildResults()
.then((result) => {
if (!result.hasSuccessfulBuild) {
return serverCtx.serve500(incomingReq, res, 'Build not successful', 'build error');
}

res.writeHead(200, 'OK');
res.write('OK');
res.end();
})
.catch(() => serverCtx.serve500(incomingReq, res, 'Error getting build results', 'ping error'));
}

if (isDevClient(req.pathname) && devServerConfig.websocket) {
return serveDevClient(devServerConfig, serverCtx, req, res);
}
Expand Down
26 changes: 26 additions & 0 deletions src/dev-server/test/req-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,32 @@ describe('request-handler', () => {
expect(h).toBe(`88mph<iframe></iframe>`);
});
});

describe('pingRoute', () => {
it('should return a 200 for successful build', async () => {
serverCtx.getBuildResults = () =>
Promise.resolve({ hasSuccessfulBuild: true }) as Promise<d.CompilerBuildResults>;

const handler = createRequestHandler(devServerConfig, serverCtx);

req.url = '/ping';

await handler(req, res);
expect(res.$statusCode).toBe(200);
});

it('should return a 500 for unsuccessful build', async () => {
serverCtx.getBuildResults = () =>
Promise.resolve({ hasSuccessfulBuild: false }) as Promise<d.CompilerBuildResults>;

const handler = createRequestHandler(devServerConfig, serverCtx);

req.url = '/ping';

await handler(req, res);
expect(res.$statusCode).toBe(500);
});
});
});

interface TestServerResponse extends ServerResponse {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const patchCloneNode = (HostElementPrototype: HTMLElement) => {
's-ol',
's-nr',
's-si',
's-rf',
];

for (; i < srcNode.childNodes.length; i++) {
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ const createElm = (oldParentVNode: d.VNode, newParentVNode: d.VNode, childIndex:
// remember the slot name, or empty string for default slot
elm['s-sn'] = newVNode.$name$ || '';

// remember the ref callback function
elm['s-rf'] = newVNode.$attrs$?.ref;

// check if we've got an old vnode for this slot
oldVNode = oldParentVNode && oldParentVNode.$children$ && oldParentVNode.$children$[childIndex];
if (oldVNode && oldVNode.$tag$ === newVNode.$tag$ && oldParentVNode.$elm$) {
Expand Down Expand Up @@ -1094,6 +1097,8 @@ render() {
}
}
}

nodeToRelocate && typeof slotRefNode['s-rf'] === 'function' && slotRefNode['s-rf'](nodeToRelocate);
} else {
// this node doesn't have a slot home to go to, so let's hide it
if (nodeToRelocate.nodeType === NODE_TYPE.ElementNode) {
Expand Down
26 changes: 20 additions & 6 deletions src/testing/puppeteer/puppeteer-screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,6 @@ export async function pageCompareScreenshot(
});
});

const screenshotOpts = createPuppeteerScreenshotOptions(opts);
const screenshotBuf = await page.screenshot(screenshotOpts);
const pixelmatchThreshold =
typeof opts.pixelmatchThreshold === 'number' ? opts.pixelmatchThreshold : screenshotBuildData.pixelmatchThreshold;

let width = emulateConfig.viewport.width;
let height = emulateConfig.viewport.height;

Expand All @@ -123,6 +118,15 @@ export async function pageCompareScreenshot(
}
}

// The width and height passed into this function will be the dimensions of the generated image
// This is _not_ guaranteed to be the viewport dimensions specified in the emulate config. If clip
// options were provided this comparison function, the width and height will be set to those clip dimensions.
// Otherwise, it will default to the emulate config viewport dimensions.
const screenshotOpts = createPuppeteerScreenshotOptions(opts, { width, height });
const screenshotBuf = await page.screenshot(screenshotOpts);
const pixelmatchThreshold =
typeof opts.pixelmatchThreshold === 'number' ? opts.pixelmatchThreshold : screenshotBuildData.pixelmatchThreshold;

const results = await compareScreenshot(
emulateConfig,
screenshotBuildData,
Expand All @@ -137,7 +141,10 @@ export async function pageCompareScreenshot(
return results;
}

function createPuppeteerScreenshotOptions(opts: ScreenshotOptions) {
export function createPuppeteerScreenshotOptions(
opts: ScreenshotOptions,
{ width, height }: { width: number; height: number },
) {
const puppeteerOpts: puppeteer.ScreenshotOptions = {
type: 'png',
fullPage: opts.fullPage,
Expand All @@ -152,6 +159,13 @@ function createPuppeteerScreenshotOptions(opts: ScreenshotOptions) {
width: opts.clip.width,
height: opts.clip.height,
};
} else {
puppeteerOpts.clip = {
x: 0,
y: 0,
width,
height,
};
}

return puppeteerOpts;
Expand Down
37 changes: 37 additions & 0 deletions src/testing/puppeteer/test/puppeteer-screenshot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createPuppeteerScreenshotOptions } from '../puppeteer-screenshot';

describe('Puppeteer Screenshot', () => {
describe('createPuppeteerScreenshotOptions', () => {
it('should use the viewport width and height by default', () => {
const options = createPuppeteerScreenshotOptions({}, { width: 800, height: 600 });

expect(options.clip).toEqual({
x: 0,
y: 0,
width: 800,
height: 600,
});
});

it('should use clip options if provided', () => {
const options = createPuppeteerScreenshotOptions(
{
clip: {
x: 10,
y: 20,
width: 100,
height: 200,
},
},
{ width: 800, height: 600 },
);

expect(options.clip).toEqual({
x: 10,
y: 20,
width: 100,
height: 200,
});
});
});
});
13 changes: 13 additions & 0 deletions test/karma/test-app/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ export namespace Components {
interface SlotParentTagChangeRoot {
"element": string;
}
interface SlotRef {
}
interface SlotReorder {
"reordered": boolean;
}
Expand Down Expand Up @@ -1190,6 +1192,12 @@ declare global {
prototype: HTMLSlotParentTagChangeRootElement;
new (): HTMLSlotParentTagChangeRootElement;
};
interface HTMLSlotRefElement extends Components.SlotRef, HTMLStencilElement {
}
var HTMLSlotRefElement: {
prototype: HTMLSlotRefElement;
new (): HTMLSlotRefElement;
};
interface HTMLSlotReorderElement extends Components.SlotReorder, HTMLStencilElement {
}
var HTMLSlotReorderElement: {
Expand Down Expand Up @@ -1420,6 +1428,7 @@ declare global {
"slot-no-default": HTMLSlotNoDefaultElement;
"slot-parent-tag-change": HTMLSlotParentTagChangeElement;
"slot-parent-tag-change-root": HTMLSlotParentTagChangeRootElement;
"slot-ref": HTMLSlotRefElement;
"slot-reorder": HTMLSlotReorderElement;
"slot-reorder-root": HTMLSlotReorderRootElement;
"slot-replace-wrapper": HTMLSlotReplaceWrapperElement;
Expand Down Expand Up @@ -1742,6 +1751,8 @@ declare namespace LocalJSX {
interface SlotParentTagChangeRoot {
"element"?: string;
}
interface SlotRef {
}
interface SlotReorder {
"reordered"?: boolean;
}
Expand Down Expand Up @@ -1902,6 +1913,7 @@ declare namespace LocalJSX {
"slot-no-default": SlotNoDefault;
"slot-parent-tag-change": SlotParentTagChange;
"slot-parent-tag-change-root": SlotParentTagChangeRoot;
"slot-ref": SlotRef;
"slot-reorder": SlotReorder;
"slot-reorder-root": SlotReorderRoot;
"slot-replace-wrapper": SlotReplaceWrapper;
Expand Down Expand Up @@ -2047,6 +2059,7 @@ declare module "@stencil/core" {
"slot-no-default": LocalJSX.SlotNoDefault & JSXBase.HTMLAttributes<HTMLSlotNoDefaultElement>;
"slot-parent-tag-change": LocalJSX.SlotParentTagChange & JSXBase.HTMLAttributes<HTMLSlotParentTagChangeElement>;
"slot-parent-tag-change-root": LocalJSX.SlotParentTagChangeRoot & JSXBase.HTMLAttributes<HTMLSlotParentTagChangeRootElement>;
"slot-ref": LocalJSX.SlotRef & JSXBase.HTMLAttributes<HTMLSlotRefElement>;
"slot-reorder": LocalJSX.SlotReorder & JSXBase.HTMLAttributes<HTMLSlotReorderElement>;
"slot-reorder-root": LocalJSX.SlotReorderRoot & JSXBase.HTMLAttributes<HTMLSlotReorderRootElement>;
"slot-replace-wrapper": LocalJSX.SlotReplaceWrapper & JSXBase.HTMLAttributes<HTMLSlotReplaceWrapperElement>;
Expand Down
24 changes: 24 additions & 0 deletions test/karma/test-app/slot-ref/cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, Element, h, Host } from '@stencil/core';

@Component({
tag: 'slot-ref',
shadow: false,
scoped: true,
})
export class SlotRef {
@Element() hostElement: HTMLElement;

render() {
return (
<Host>
<slot
name="title"
ref={(el) => {
this.hostElement.setAttribute('data-ref-id', el.id);
this.hostElement.setAttribute('data-ref-tagname', el.tagName);
}}
/>
</Host>
);
}
}
6 changes: 6 additions & 0 deletions test/karma/test-app/slot-ref/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="./build/testapp.esm.js" type="module"></script>
<script src="./build/testapp.js" nomodule></script>

<slot-ref><span slot="title" id="slotted-element-id">Hello World!</span></slot-ref>
19 changes: 19 additions & 0 deletions test/karma/test-app/slot-ref/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setupDomTests, waitForChanges } from '../util';

describe('slot-ref', function () {
const { setupDom, tearDownDom } = setupDomTests(document);
let app: HTMLElement;

beforeEach(async () => {
app = await setupDom('/slot-ref/index.html');
});
afterEach(tearDownDom);

it('ref callback of slot is called', async () => {
await waitForChanges();

const host = app.querySelector('slot-ref');
expect(host.getAttribute('data-ref-id')).toBe('slotted-element-id');
expect(host.getAttribute('data-ref-tagname')).toBe('SPAN');
});
});

0 comments on commit 6d0e6c3

Please sign in to comment.