Skip to content

Merge pull request #2698 from gluestack/release/@gluestack-ui/pin-inp… #168

Merge pull request #2698 from gluestack/release/@gluestack-ui/pin-inp…

Merge pull request #2698 from gluestack/release/@gluestack-ui/pin-inp… #168

Workflow file for this run

name: Next.js Latest
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
schedule:
- cron: '0 0 * * *' # Run daily at midnight UTC
jobs:
check-next-version:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
latest_version: ${{ steps.check.outputs.latest_version }}
steps:
- id: check
run: |
LATEST=$(npm view next version)
CURRENT=$(cat .next-version 2>/dev/null || echo "")
echo "Latest version: $LATEST"
echo "Current version: $CURRENT"
if [ "$LATEST" != "$CURRENT" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "latest_version=$LATEST" >> $GITHUB_OUTPUT
echo $LATEST > .next-version
else
echo "should_run=false" >> $GITHUB_OUTPUT
fi
test-next-latest:
needs: check-next-version
if: ${{ needs.check-next-version.outputs.should_run == 'true' || github.event_name == 'push' || github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
name: Next.js latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Print Environment Info
run: |
node -v
npm -v
cat .next-version || echo ".next-version file not found"
- name: Create Next.js project
run: |
npx create-next-app@latest test-app --typescript --eslint --tailwind --app --src-dir --use-npm --no-experimental-app
cd test-app
- name: Install Gluestack UI
working-directory: test-app
run: |
npx gluestack-ui init --template-only --projectType nextjs
npx gluestack-ui add --all
- name: Rename original next.config file
working-directory: test-app
run: mv next.config.ts next.config.original.ts
- name: Create new next.config file
working-directory: test-app
run: |
cat <<EOT > next.config.ts
import { withGluestackUI } from "@gluestack/ui-next-adapter";
const nextConfig = {
transpilePackages: ["nativewind", "react-native-css-interop"],
typescript: {
ignoreBuildErrors: true,
},
};
export default withGluestackUI(nextConfig);
EOT
- name: Copy data to src/app/registry.tsx
working-directory: test-app
run: |
cat <<EOT > src/app/registry.tsx
"use client";
import React, { useRef, useState } from "react";
import { useServerInsertedHTML } from "next/navigation";
import { StyleRegistry, createStyleRegistry } from "styled-jsx";
// @ts-ignore
import { AppRegistry } from "react-native-web";
import { flush } from "@gluestack-ui/nativewind-utils/flush";
export default function StyledJsxRegistry({
children,
}: {
children: React.ReactNode;
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [jsxStyleRegistry] = useState(() => createStyleRegistry());
const isServerInserted = useRef(false);
useServerInsertedHTML(() => {
AppRegistry.registerComponent("Main", () => "main");
const { getStyleElement } = AppRegistry.getApplication("Main");
if (!isServerInserted.current) {
isServerInserted.current = true;
const styles = [getStyleElement(), jsxStyleRegistry.styles(), flush()];
jsxStyleRegistry.flush();
return <>{styles}</>;
}
});
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>;
}
EOT
- name: Copy data to components/ui/gluestack-ui-provider/index.web.tsx
working-directory: test-app
run: |
cat <<EOT > components/ui/gluestack-ui-provider/index.web.tsx
"use client";
import React, { useEffect, useLayoutEffect } from "react";
import { config } from "./config";
import { OverlayProvider } from "@gluestack-ui/overlay";
import { ToastProvider } from "@gluestack-ui/toast";
import { setFlushStyles } from "@gluestack-ui/nativewind-utils/flush";
import { script } from "./script";
const variableStyleTagId = "nativewind-style";
const createStyle = (styleTagId: string) => {
const style = document.createElement("style");
style.id = styleTagId;
style.appendChild(document.createTextNode(""));
return style;
};
export const useSafeLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect;
export function GluestackUIProvider({
mode = "light",
...props
}: {
mode?: "light" | "dark" | "system";
children?: React.ReactNode;
}) {
let cssVariablesWithMode = "";
Object.keys(config).forEach((configKey) => {
cssVariablesWithMode += configKey === "dark" ? ".dark {" : ":root {";
const cssVariables = Object.keys(
config[configKey as keyof typeof config]
).reduce((acc: string, curr: string) => {
acc += curr + ":" + config[configKey as keyof typeof config][curr];
return acc;
}, "");
cssVariablesWithMode += cssVariables+ "\n}";
});
setFlushStyles(cssVariablesWithMode);
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
script(e.matches ? "dark" : "light");
}, []);
useSafeLayoutEffect(() => {
if (mode !== "system") {
const documentElement = document.documentElement;
if (documentElement) {
documentElement.classList.add(mode);
documentElement.classList.remove(mode === "light" ? "dark" : "light");
documentElement.style.colorScheme = mode;
}
}
}, [mode]);
useSafeLayoutEffect(() => {
if (mode !== "system") return;
const media = window.matchMedia("(prefers-color-scheme: dark)");
media.addListener(handleMediaQuery);
return () => media.removeListener(handleMediaQuery);
}, [handleMediaQuery]);
useSafeLayoutEffect(() => {
if (typeof window !== "undefined") {
const documentElement = document.documentElement;
if (documentElement) {
const head = documentElement.querySelector("head");
let style = head?.querySelector("[id="+variableStyleTagId+"]");
if (!style) {
style = createStyle(variableStyleTagId);
style.innerHTML = cssVariablesWithMode;
if (head) head.appendChild(style);
}
}
}
}, []);
return (
<>
<OverlayProvider>
<ToastProvider>{props.children}</ToastProvider>
</OverlayProvider>
</>
);
}
EOT
- name: Create patches folder
working-directory: test-app
run: |
mkdir -p patches
- name: Create react-native-web patch file
working-directory: test-app/patches
run: |
cat <<EOT > react-native-web+0.19.13.patch
diff --git a/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js b/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
index 0c0cb2f..83fd94b 100644
--- a/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
+++ b/node_modules/react-native-web/dist/cjs/exports/AppRegistry/renderApplication.js
@@ -1,6 +1,5 @@
"use strict";
-var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.default = renderApplication;
@@ -8,7 +7,7 @@ exports.getApplication = getApplication;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _AppContainer = _interopRequireDefault(require("./AppContainer"));
var _invariant = _interopRequireDefault(require("fbjs/lib/invariant"));
-var _render = _interopRequireWildcard(require("../render"));
+var _render = require("../render");
var _StyleSheet = _interopRequireDefault(require("../StyleSheet"));
var _react = _interopRequireDefault(require("react"));
/**
@@ -24,9 +23,8 @@ var _react = _interopRequireDefault(require("react"));
function renderApplication(RootComponent, WrapperComponent, callback, options) {
var shouldHydrate = options.hydrate,
initialProps = options.initialProps,
- mode = options.mode,
rootTag = options.rootTag;
- var renderFn = shouldHydrate ? mode === 'concurrent' ? _render.hydrate : _render.hydrateLegacy : mode === 'concurrent' ? _render.render : _render.default;
+ var renderFn = shouldHydrate ? _render.hydrate : _render.render;
(0, _invariant.default)(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
return renderFn(/*#__PURE__*/_react.default.createElement(_AppContainer.default, {
WrapperComponent: WrapperComponent,
diff --git a/node_modules/react-native-web/dist/cjs/exports/render/index.js b/node_modules/react-native-web/dist/cjs/exports/render/index.js
index b41ee11..18d9b2f 100644
--- a/node_modules/react-native-web/dist/cjs/exports/render/index.js
+++ b/node_modules/react-native-web/dist/cjs/exports/render/index.js
@@ -10,15 +10,10 @@
'use client';
-var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
-exports.default = renderLegacy;
+exports.default = render;
exports.hydrate = hydrate;
-exports.hydrateLegacy = hydrateLegacy;
-exports.render = render;
-var _reactDom = require("react-dom");
var _client = require("react-dom/client");
-var _unmountComponentAtNode = _interopRequireDefault(require("../unmountComponentAtNode"));
var _dom = require("../StyleSheet/dom");
function hydrate(element, root) {
(0, _dom.createSheet)(root);
@@ -30,21 +25,3 @@ function render(element, root) {
reactRoot.render(element);
return reactRoot;
}
\ No newline at end of file
-function hydrateLegacy(element, root, callback) {
- (0, _dom.createSheet)(root);
- (0, _reactDom.hydrate)(element, root, callback);
- return {
- unmount: function unmount() {
- return (0, _unmountComponentAtNode.default)(root);
- }
- };
-}
-function renderLegacy(element, root, callback) {
- (0, _dom.createSheet)(root);
- (0, _reactDom.render)(element, root, callback);
- return {
- unmount: function unmount() {
- return (0, _unmountComponentAtNode.default)(root);
- }
- };
-}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
index 3ea3964..e740204 100644
--- a/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
+++ b/node_modules/react-native-web/dist/cjs/exports/unmountComponentAtNode/index.js
@@ -1,8 +1,7 @@
"use strict";
exports.__esModule = true;
-exports.default = void 0;
-var _reactDom = require("react-dom");
+exports.default = unmountComponentAtNode;
/**
* Copyright (c) Nicolas Gallagher.
*
@@ -11,5 +10,9 @@ var _reactDom = require("react-dom");
*
*
*/
-var _default = exports.default = _reactDom.unmountComponentAtNode;
+
+function unmountComponentAtNode(rootTag) {
+ rootTag.unmount();
+ return true;
+}
module.exports = exports.default;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
index b53dff6..c56c1dc 100644
--- a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
+++ b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js
@@ -11,15 +11,14 @@ import _extends from "@babel/runtime/helpers/extends";
import AppContainer from './AppContainer';
import invariant from 'fbjs/lib/invariant';
-import renderLegacy, { hydrateLegacy, render, hydrate } from '../render';
+import { render, hydrate } from '../render';
import StyleSheet from '../StyleSheet';
import React from 'react';
export default function renderApplication(RootComponent, WrapperComponent, callback, options) {
var shouldHydrate = options.hydrate,
initialProps = options.initialProps,
- mode = options.mode,
rootTag = options.rootTag;
- var renderFn = shouldHydrate ? mode === 'concurrent' ? hydrate : hydrateLegacy : mode === 'concurrent' ? render : renderLegacy;
+ var renderFn = shouldHydrate ? hydrate : render;
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
return renderFn(/*#__PURE__*/React.createElement(AppContainer, {
WrapperComponent: WrapperComponent,
diff --git a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
index b9df2af..2b671ba 100644
--- a/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
+++ b/node_modules/react-native-web/dist/exports/AppRegistry/renderApplication.js.flow
@@ -11,7 +11,7 @@
import type { ComponentType, Node } from 'react';
import AppContainer from './AppContainer';
import invariant from 'fbjs/lib/invariant';
-import renderLegacy, { hydrateLegacy, render, hydrate } from '../render';
+import { render, hydrate } from '../render';
import StyleSheet from '../StyleSheet';
import React from 'react';
export type Application = {
@@ -20,7 +20,6 @@ export type Application = {
declare export default function renderApplication<Props: Object>(RootComponent: ComponentType<Props>, WrapperComponent?: ?ComponentType<*>, callback?: () => void, options: {
hydrate: boolean,
initialProps: Props,
- mode: 'concurrent' | 'legacy',
rootTag: any,
}): Application;
declare export function getApplication(RootComponent: ComponentType<Object>, initialProps: Object, WrapperComponent?: ?ComponentType<*>): {|
diff --git a/node_modules/react-native-web/dist/exports/render/index.js b/node_modules/react-native-web/dist/exports/render/index.js
index aa91a2a..8f9a14d 100644
--- a/node_modules/react-native-web/dist/exports/render/index.js
+++ b/node_modules/react-native-web/dist/exports/render/index.js
@@ -9,35 +9,15 @@
'use client';
-import { hydrate as domLegacyHydrate, render as domLegacyRender } from 'react-dom';
import { createRoot as domCreateRoot, hydrateRoot as domHydrateRoot } from 'react-dom/client';
-import unmountComponentAtNode from '../unmountComponentAtNode';
import { createSheet } from '../StyleSheet/dom';
export function hydrate(element, root) {
createSheet(root);
return domHydrateRoot(root, element);
}
-export function render(element, root) {
+export default function render(element, root) {
createSheet(root);
var reactRoot = domCreateRoot(root);
reactRoot.render(element);
return reactRoot;
}
\ No newline at end of file
-export function hydrateLegacy(element, root, callback) {
- createSheet(root);
- domLegacyHydrate(element, root, callback);
- return {
- unmount: function unmount() {
- return unmountComponentAtNode(root);
- }
- };
-}
-export default function renderLegacy(element, root, callback) {
- createSheet(root);
- domLegacyRender(element, root, callback);
- return {
- unmount: function unmount() {
- return unmountComponentAtNode(root);
- }
- };
-}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/render/index.js.flow b/node_modules/react-native-web/dist/exports/render/index.js.flow
index 1bd771e..729d57d 100644
--- a/node_modules/react-native-web/dist/exports/render/index.js.flow
+++ b/node_modules/react-native-web/dist/exports/render/index.js.flow
@@ -9,11 +9,7 @@
'use client';
-import { hydrate as domLegacyHydrate, render as domLegacyRender } from 'react-dom';
import { createRoot as domCreateRoot, hydrateRoot as domHydrateRoot } from 'react-dom/client';
-import unmountComponentAtNode from '../unmountComponentAtNode';
import { createSheet } from '../StyleSheet/dom';
declare export function hydrate(element: any, root: any): any;
-declare export function render(element: any, root: any): any;
-declare export function hydrateLegacy(element: any, root: any, callback: any): any;
-declare export default function renderLegacy(element: any, root: any, callback: any): any;
\ No newline at end of file
+declare export default function render(element: any, root: any): any;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
index 925051c..cea4dee 100644
--- a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
+++ b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js
@@ -7,5 +7,7 @@
*
*/
-import { unmountComponentAtNode } from 'react-dom';
-export default unmountComponentAtNode;
\ No newline at end of file
+export default function unmountComponentAtNode(rootTag) {
+ rootTag.unmount();
+ return true;
+}
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
index b950090..90ec151 100644
--- a/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
+++ b/node_modules/react-native-web/dist/exports/unmountComponentAtNode/index.js.flow
@@ -6,6 +6,4 @@
*
* @noflow
*/
-
-import { unmountComponentAtNode } from 'react-dom';
-export default unmountComponentAtNode;
\ No newline at end of file
+declare export default function unmountComponentAtNode(rootTag: any): any;
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow
new file mode 100644
index 0000000..9eb6144
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.js.flow
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import { act, render } from '@testing-library/react';
+import { createEventTarget } from 'dom-event-testing-library';
+import { addEventListener } from '..';
+describe('addEventListener', () => {
+ describe('addEventListener()', () => {
+ test('event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('event dispatched on parent', () => {
+ const listener = jest.fn();
+ const listenerCapture = jest.fn();
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const parent = createEventTarget(parentRef.current);
+ act(() => {
+ parent.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ expect(listenerCapture).toBeCalledTimes(0);
+ });
+ test('event dispatched on child', () => {
+ const log = [];
+ const listener = jest.fn(() => {
+ log.push('bubble');
+ });
+ const listenerCapture = jest.fn(() => {
+ log.push('capture');
+ });
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(log).toEqual(['capture', 'bubble']);
+ });
+ test('event dispatched on text node', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const text = createEventTarget(childRef.current.firstChild);
+ act(() => {
+ text.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to document', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={document} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to window ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={window} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('custom event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ act(() => {
+ const event = new CustomEvent('magic-event', {
+ bubbles: true
+ });
+ targetRef.current.dispatchEvent(event);
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listeners can be set on multiple targets simultaneously', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', e.currentTarget.id]);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', e.currentTarget.id]);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(2);
+ expect(listener).toBeCalledTimes(2);
+ expect(log).toEqual([['capture', 'parent'], ['capture', 'target'], ['bubble', 'target'], ['bubble', 'parent']]);
+ });
+ test('listeners are specific to each event handle', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', 'target']);
+ });
+ const listenerAlt = jest.fn(e => {
+ log.push(['bubble', 'target-alt']);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', 'target']);
+ });
+ const listenerCaptureAlt = jest.fn(e => {
+ log.push(['capture', 'target-alt']);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listenerCaptureAlt).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ expect(log).toEqual([['capture', 'target'], ['capture', 'target-alt'], ['bubble', 'target'], ['bubble', 'target-alt']]);
+ });
+ });
+ describe('stopPropagation and stopImmediatePropagation', () => {
+ test('stopPropagation works as expected', () => {
+ const childListener = jest.fn(e => {
+ e.stopPropagation();
+ });
+ const targetListener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(childListener).toBeCalledTimes(1);
+ expect(targetListener).toBeCalledTimes(0);
+ });
+ test('stopImmediatePropagation works as expected', () => {
+ const firstListener = jest.fn(e => {
+ e.stopImmediatePropagation();
+ });
+ const secondListener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(firstListener).toBeCalledTimes(1);
+ expect(secondListener).toBeCalledTimes(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow
new file mode 100644
index 0000000..c1805b7
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/addEventListener/__tests__/index-test.node.js.flow
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import * as ReactDOMServer from 'react-dom/server';
+import { addEventListener } from '..';
+describe('addEventListener', () => {
+ test('can render correctly using ReactDOMServer', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ const output = ReactDOMServer.renderToString(<Component />);
+ expect(output).toBe('<div></div>');
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow
new file mode 100644
index 0000000..9b57364
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/useEvent/__tests__/index-test.js.flow
@@ -0,0 +1,247 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import * as React from 'react';
+import { act, render } from '@testing-library/react';
+import { createEventTarget } from 'dom-event-testing-library';
+import useEvent from '..';
+describe('use-event', () => {
+ describe('setListener()', () => {
+ test('event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('event dispatched on parent', () => {
+ const listener = jest.fn();
+ const listenerCapture = jest.fn();
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const parent = createEventTarget(parentRef.current);
+ act(() => {
+ parent.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ expect(listenerCapture).toBeCalledTimes(0);
+ });
+ test('event dispatched on child', () => {
+ const log = [];
+ const listener = jest.fn(() => {
+ log.push('bubble');
+ });
+ const listenerCapture = jest.fn(() => {
+ log.push('capture');
+ });
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(log).toEqual(['capture', 'bubble']);
+ });
+ test('event dispatched on text node', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const text = createEventTarget(childRef.current.firstChild);
+ act(() => {
+ text.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to document ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={document} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener can be attached to window ', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ render(<Component target={window} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listener is replaceable', () => {
+ const listener = jest.fn();
+ const listenerAlt = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ const {
+ rerender
+ } = render(<Component onClick={listener} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ rerender(<Component onClick={listenerAlt} />);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ });
+ test('listener is removed when value is null', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(arg0: any): any;
+ const {
+ unmount
+ } = render(<Component off={false} />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(1);
+
+ // this should unset the listener
+ unmount();
+ listener.mockClear();
+ act(() => {
+ target.click();
+ });
+ expect(listener).toBeCalledTimes(0);
+ });
+ test('custom event dispatched on target', () => {
+ const listener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ act(() => {
+ const event = new CustomEvent('magic-event', {
+ bubbles: true
+ });
+ targetRef.current.dispatchEvent(event);
+ });
+ expect(listener).toBeCalledTimes(1);
+ });
+ test('listeners can be set on multiple targets simultaneously', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const parentRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', e.currentTarget.id]);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', e.currentTarget.id]);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(2);
+ expect(listener).toBeCalledTimes(2);
+ expect(log).toEqual([['capture', 'parent'], ['capture', 'target'], ['bubble', 'target'], ['bubble', 'parent']]);
+ });
+ test('listeners are specific to each event handle', () => {
+ const log = [];
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ const listener = jest.fn(e => {
+ log.push(['bubble', 'target']);
+ });
+ const listenerAlt = jest.fn(e => {
+ log.push(['bubble', 'target-alt']);
+ });
+ const listenerCapture = jest.fn(e => {
+ log.push(['capture', 'target']);
+ });
+ const listenerCaptureAlt = jest.fn(e => {
+ log.push(['capture', 'target-alt']);
+ });
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(listenerCapture).toBeCalledTimes(1);
+ expect(listenerCaptureAlt).toBeCalledTimes(1);
+ expect(listener).toBeCalledTimes(1);
+ expect(listenerAlt).toBeCalledTimes(1);
+ expect(log).toEqual([['capture', 'target'], ['capture', 'target-alt'], ['bubble', 'target'], ['bubble', 'target-alt']]);
+ });
+ });
+ describe('cleanup', () => {
+ test('removes all listeners for given event type from targets', () => {
+ const clickListener = jest.fn();
+ declare function Component(): any;
+ const {
+ unmount
+ } = render(<Component />);
+ unmount();
+ const target = createEventTarget(document);
+ act(() => {
+ target.click();
+ });
+ expect(clickListener).toBeCalledTimes(0);
+ });
+ });
+ describe('stopPropagation and stopImmediatePropagation', () => {
+ test('stopPropagation works as expected', () => {
+ const childListener = jest.fn(e => {
+ e.stopPropagation();
+ });
+ const targetListener = jest.fn();
+ const targetRef = React.createRef();
+ const childRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const child = createEventTarget(childRef.current);
+ act(() => {
+ child.click();
+ });
+ expect(childListener).toBeCalledTimes(1);
+ expect(targetListener).toBeCalledTimes(0);
+ });
+ test('stopImmediatePropagation works as expected', () => {
+ const firstListener = jest.fn(e => {
+ e.stopImmediatePropagation();
+ });
+ const secondListener = jest.fn();
+ const targetRef = React.createRef();
+ declare function Component(): any;
+ render(<Component />);
+ const target = createEventTarget(targetRef.current);
+ act(() => {
+ target.click();
+ });
+ expect(firstListener).toBeCalledTimes(1);
+ expect(secondListener).toBeCalledTimes(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow b/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow
new file mode 100644
index 0000000..49edfc6
--- /dev/null
+++ b/node_modules/react-native-web/dist/modules/useStable/__tests__/index-test.js.flow
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+import { render } from '@testing-library/react';
+import useStable from '..';
+describe('useStable', () => {
+ let spy = {};
+ declare var TestComponent: (arg0: any) => React.Node;
+ beforeEach(() => {
+ spy = {};
+ });
+ test('correctly sets the initial value', () => {
+ declare var initialValueCallback: () => any;
+ render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(5);
+ });
+ test('does not change the value', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(0);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(0);
+ });
+ test('only calls the callback once', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(counter).toBe(1);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(counter).toBe(1);
+ });
+ test('does not change the value if the callback initially returns null', () => {
+ let counter = 0;
+ declare var initialValueCallback: () => any;
+ const {
+ rerender
+ } = render(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(null);
+ rerender(<TestComponent initialValueCallback={initialValueCallback} />);
+ expect(spy.value).toBe(null);
+ });
+});
\ No newline at end of file
EOT
- name: Add Button component
working-directory: test-app
run: |
cat <<EOT > src/app/page.tsx
import {
Button,
ButtonText,
ButtonSpinner,
ButtonIcon,
ButtonGroup,
} from "@/components/ui/button";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Button size="md" variant="solid" action="primary">
<ButtonText>Hello World!</ButtonText>
</Button>
</main>
);
}
EOT
- name: Add postinstall script to package.json
working-directory: test-app
run: |
jq '.scripts.postinstall = "patch-package"' package.json > tmp.$$.json && mv tmp.$$.json package.json
- name: Run postinstall to apply patches
working-directory: test-app
run: npm run postinstall
- name: Install Babel dependencies
working-directory: test-app
run: |
npm install @babel/preset-react babel-loader --save-dev
- name: Install dependencies and clean cache
working-directory: test-app
run: |
npm install
# Clean npm cache to avoid potential issues with older versions
npm cache clean --force
- name: Build Next.js app
working-directory: test-app
env:
NEXT_TELEMETRY_DISABLED: 1
run: |
echo "{ \"extends\": \"next/core-web-vitals\", \"rules\": {} }" > .eslintrc.json
npm run build -- --no-lint
- name: Start Next.js app
working-directory: test-app
run: |
npm run start &
sleep 20
- name: Fetch and Log Server Response
run: |
curl -s http://localhost:3000 || echo "Failed to reach the server"
curl -s http://localhost:3000 > server_response.html
cat server_response.html
- name: Check if button is rendered
run: |
RESPONSE=$(curl -s http://localhost:3000)
echo "$RESPONSE" | grep -q "Hello World!" && echo "Button found" || (echo "Button not found" && exit 1)
notify:
needs: test-next-latest
if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Next.js Latest Test: ${{ job.status }}'
fields: repo,commit,action,eventName
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}