mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 03:27:23 +01:00
Compare commits
33 Commits
toaster/fi
...
claude/yog
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5478bb23d0 | ||
|
|
aebff0e7db | ||
|
|
74e9d8a47e | ||
|
|
1c5c520830 | ||
|
|
39ed8963bd | ||
|
|
c619b7cc06 | ||
|
|
797410e56c | ||
|
|
42de479784 | ||
|
|
6eb38d9042 | ||
|
|
79b091afc1 | ||
|
|
41124ac14c | ||
|
|
c8c1445cdf | ||
|
|
ef0f050935 | ||
|
|
99cf796bc4 | ||
|
|
cfaeddb398 | ||
|
|
9dd913e575 | ||
|
|
daf5d8c880 | ||
|
|
7620bbd070 | ||
|
|
3fe3f63224 | ||
|
|
3ff238f47a | ||
|
|
cbde3d5d0d | ||
|
|
b7475d8768 | ||
|
|
4494170f74 | ||
|
|
9484218ba4 | ||
|
|
2a5e8ef38c | ||
|
|
a84f12b816 | ||
|
|
0f43ea9bec | ||
|
|
0889897a1c | ||
|
|
68f2ea4b95 | ||
|
|
d4ebfd9771 | ||
|
|
e3c25260ed | ||
|
|
1bded85718 | ||
|
|
cf6cdbbbad |
38
bench/snippets/buffer-slice.mjs
Normal file
38
bench/snippets/buffer-slice.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
// @runtime bun,node
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
const small = Buffer.alloc(64, 0x42);
|
||||
const medium = Buffer.alloc(1024, 0x42);
|
||||
const large = Buffer.alloc(1024 * 1024, 0x42);
|
||||
|
||||
group("slice - no args", () => {
|
||||
bench("Buffer(64).slice()", () => small.slice());
|
||||
bench("Buffer(1024).slice()", () => medium.slice());
|
||||
bench("Buffer(1M).slice()", () => large.slice());
|
||||
});
|
||||
|
||||
group("slice - one int arg", () => {
|
||||
bench("Buffer(64).slice(10)", () => small.slice(10));
|
||||
bench("Buffer(1024).slice(10)", () => medium.slice(10));
|
||||
bench("Buffer(1M).slice(1024)", () => large.slice(1024));
|
||||
});
|
||||
|
||||
group("slice - two int args", () => {
|
||||
bench("Buffer(64).slice(10, 50)", () => small.slice(10, 50));
|
||||
bench("Buffer(1024).slice(10, 100)", () => medium.slice(10, 100));
|
||||
bench("Buffer(1M).slice(1024, 4096)", () => large.slice(1024, 4096));
|
||||
});
|
||||
|
||||
group("slice - negative args", () => {
|
||||
bench("Buffer(64).slice(-10)", () => small.slice(-10));
|
||||
bench("Buffer(1024).slice(-100, -10)", () => medium.slice(-100, -10));
|
||||
bench("Buffer(1M).slice(-4096, -1024)", () => large.slice(-4096, -1024));
|
||||
});
|
||||
|
||||
group("subarray - two int args", () => {
|
||||
bench("Buffer(64).subarray(10, 50)", () => small.subarray(10, 50));
|
||||
bench("Buffer(1024).subarray(10, 100)", () => medium.subarray(10, 100));
|
||||
bench("Buffer(1M).subarray(1024, 4096)", () => large.subarray(1024, 4096));
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -33,7 +33,23 @@ var testArray = [
|
||||
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
bench("structuredClone(array)", () => structuredClone(testArray));
|
||||
bench("structuredClone(nested array)", () => structuredClone(testArray));
|
||||
bench("structuredClone(123)", () => structuredClone(123));
|
||||
bench("structuredClone({a: 123})", () => structuredClone({ a: 123 }));
|
||||
|
||||
// Array fast path targets
|
||||
var numbersSmall = Array.from({ length: 10 }, (_, i) => i);
|
||||
var numbersMedium = Array.from({ length: 100 }, (_, i) => i);
|
||||
var numbersLarge = Array.from({ length: 1000 }, (_, i) => i);
|
||||
var stringsSmall = Array.from({ length: 10 }, (_, i) => `item-${i}`);
|
||||
var stringsMedium = Array.from({ length: 100 }, (_, i) => `item-${i}`);
|
||||
var mixed = [1, "hello", true, null, undefined, 3.14, "world", false, 42, "test"];
|
||||
|
||||
bench("structuredClone([10 numbers])", () => structuredClone(numbersSmall));
|
||||
bench("structuredClone([100 numbers])", () => structuredClone(numbersMedium));
|
||||
bench("structuredClone([1000 numbers])", () => structuredClone(numbersLarge));
|
||||
bench("structuredClone([10 strings])", () => structuredClone(stringsSmall));
|
||||
bench("structuredClone([100 strings])", () => structuredClone(stringsMedium));
|
||||
bench("structuredClone([10 mixed])", () => structuredClone(mixed));
|
||||
|
||||
await run();
|
||||
|
||||
@@ -61,6 +61,7 @@ set(BUN_DEPENDENCIES
|
||||
LibArchive # must be loaded after zlib
|
||||
HdrHistogram # must be loaded after zlib
|
||||
Zstd
|
||||
Yoga
|
||||
)
|
||||
|
||||
# TinyCC is optional - disabled on Windows ARM64 where it's not supported
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/mimalloc
|
||||
COMMIT
|
||||
ffa38ab8ac914f9eb7af75c1f8ad457643dc14f2
|
||||
1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a
|
||||
)
|
||||
|
||||
set(MIMALLOC_CMAKE_ARGS
|
||||
@@ -14,7 +14,7 @@ set(MIMALLOC_CMAKE_ARGS
|
||||
-DMI_BUILD_TESTS=OFF
|
||||
-DMI_USE_CXX=ON
|
||||
-DMI_SKIP_COLLECT_ON_EXIT=ON
|
||||
|
||||
|
||||
# ```
|
||||
# ❯ mimalloc_allow_large_os_pages=0 BUN_PORT=3004 mem bun http-hello.js
|
||||
# Started development server: http://localhost:3004
|
||||
@@ -51,7 +51,7 @@ if(ENABLE_ASAN)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
|
||||
elseif(APPLE OR LINUX)
|
||||
if(APPLE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
else()
|
||||
@@ -87,9 +87,9 @@ endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
set(MIMALLOC_LIBRARY mimalloc-debug)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static-debug)
|
||||
else()
|
||||
set(MIMALLOC_LIBRARY mimalloc)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static)
|
||||
endif()
|
||||
elseif(DEBUG)
|
||||
if (ENABLE_ASAN)
|
||||
|
||||
26
cmake/targets/BuildYoga.cmake
Normal file
26
cmake/targets/BuildYoga.cmake
Normal file
@@ -0,0 +1,26 @@
|
||||
register_repository(
|
||||
NAME
|
||||
yoga
|
||||
REPOSITORY
|
||||
facebook/yoga
|
||||
COMMIT
|
||||
dc2581f229cb05c7d2af8dee37b2ee0b59fd5326
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
yoga
|
||||
TARGETS
|
||||
yogacore
|
||||
ARGS
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
-DYOGA_BUILD_TESTS=OFF
|
||||
-DYOGA_BUILD_SAMPLES=OFF
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LIB_PATH
|
||||
yoga
|
||||
LIBRARIES
|
||||
yogacore
|
||||
INCLUDES
|
||||
.
|
||||
)
|
||||
@@ -66,9 +66,9 @@ string(REPLACE "\\r" "\\\\r" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
|
||||
string(REPLACE "\\t" "\\\\t" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
|
||||
string(REPLACE "${BKSLASH_PLACEHOLDER}" "\\\\" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
|
||||
|
||||
string(JSON BUILDKITE_BUILD_UUID GET ${BUILDKITE_BUILD} id)
|
||||
string(JSON BUILDKITE_JOBS GET ${BUILDKITE_BUILD} jobs)
|
||||
string(JSON BUILDKITE_JOBS_COUNT LENGTH ${BUILDKITE_JOBS})
|
||||
string(JSON BUILDKITE_BUILD_UUID GET "${BUILDKITE_BUILD}" id)
|
||||
string(JSON BUILDKITE_JOBS GET "${BUILDKITE_BUILD}" jobs)
|
||||
string(JSON BUILDKITE_JOBS_COUNT LENGTH "${BUILDKITE_JOBS}")
|
||||
|
||||
if(NOT BUILDKITE_JOBS_COUNT GREATER 0)
|
||||
message(FATAL_ERROR "No jobs found: ${BUILDKITE_BUILD_URL}")
|
||||
@@ -83,14 +83,14 @@ set(BUILDKITE_JOBS_MATCH)
|
||||
|
||||
math(EXPR BUILDKITE_JOBS_MAX_INDEX "${BUILDKITE_JOBS_COUNT} - 1")
|
||||
foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
|
||||
string(JSON BUILDKITE_JOB GET ${BUILDKITE_JOBS} ${i})
|
||||
string(JSON BUILDKITE_JOB_ID GET ${BUILDKITE_JOB} id)
|
||||
string(JSON BUILDKITE_JOB_PASSED GET ${BUILDKITE_JOB} passed)
|
||||
string(JSON BUILDKITE_JOB_GROUP_ID GET ${BUILDKITE_JOB} group_uuid)
|
||||
string(JSON BUILDKITE_JOB_GROUP_KEY GET ${BUILDKITE_JOB} group_identifier)
|
||||
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} step_key)
|
||||
string(JSON BUILDKITE_JOB GET "${BUILDKITE_JOBS}" ${i})
|
||||
string(JSON BUILDKITE_JOB_ID GET "${BUILDKITE_JOB}" id)
|
||||
string(JSON BUILDKITE_JOB_PASSED GET "${BUILDKITE_JOB}" passed)
|
||||
string(JSON BUILDKITE_JOB_GROUP_ID GET "${BUILDKITE_JOB}" group_uuid)
|
||||
string(JSON BUILDKITE_JOB_GROUP_KEY GET "${BUILDKITE_JOB}" group_identifier)
|
||||
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" step_key)
|
||||
if(NOT BUILDKITE_JOB_NAME)
|
||||
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} name)
|
||||
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" name)
|
||||
endif()
|
||||
|
||||
if(NOT BUILDKITE_JOB_PASSED)
|
||||
@@ -121,7 +121,7 @@ foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
|
||||
endif()
|
||||
|
||||
file(READ ${BUILDKITE_ARTIFACTS_PATH} BUILDKITE_ARTIFACTS)
|
||||
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH ${BUILDKITE_ARTIFACTS})
|
||||
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH "${BUILDKITE_ARTIFACTS}")
|
||||
if(NOT BUILDKITE_ARTIFACTS_LENGTH GREATER 0)
|
||||
list(APPEND BUILDKITE_JOBS_NO_ARTIFACTS ${BUILDKITE_JOB_NAME})
|
||||
continue()
|
||||
@@ -129,9 +129,9 @@ foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
|
||||
|
||||
math(EXPR BUILDKITE_ARTIFACTS_MAX_INDEX "${BUILDKITE_ARTIFACTS_LENGTH} - 1")
|
||||
foreach(i RANGE 0 ${BUILDKITE_ARTIFACTS_MAX_INDEX})
|
||||
string(JSON BUILDKITE_ARTIFACT GET ${BUILDKITE_ARTIFACTS} ${i})
|
||||
string(JSON BUILDKITE_ARTIFACT_ID GET ${BUILDKITE_ARTIFACT} id)
|
||||
string(JSON BUILDKITE_ARTIFACT_PATH GET ${BUILDKITE_ARTIFACT} path)
|
||||
string(JSON BUILDKITE_ARTIFACT GET "${BUILDKITE_ARTIFACTS}" ${i})
|
||||
string(JSON BUILDKITE_ARTIFACT_ID GET "${BUILDKITE_ARTIFACT}" id)
|
||||
string(JSON BUILDKITE_ARTIFACT_PATH GET "${BUILDKITE_ARTIFACT}" path)
|
||||
|
||||
if(NOT BUILDKITE_ARTIFACT_PATH MATCHES "\\.(o|a|lib|zip|tar|gz)")
|
||||
continue()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.10",
|
||||
"workspaces": [
|
||||
"./packages/bun-types",
|
||||
"./packages/@types/bun"
|
||||
|
||||
@@ -95,12 +95,12 @@ export const platforms: Platform[] = [
|
||||
bin: "bun-windows-x64-baseline",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
{
|
||||
os: "win32",
|
||||
arch: "arm64",
|
||||
bin: "bun-windows-aarch64",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
// {
|
||||
// os: "win32",
|
||||
// arch: "arm64",
|
||||
// bin: "bun-windows-aarch64",
|
||||
// exe: "bin/bun.exe",
|
||||
// },
|
||||
];
|
||||
|
||||
export const supportedPlatforms: Platform[] = platforms
|
||||
|
||||
85
packages/bun-types/bun.d.ts
vendored
85
packages/bun-types/bun.d.ts
vendored
@@ -2445,7 +2445,12 @@ declare module "bun" {
|
||||
/**
|
||||
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||
*/
|
||||
interface BuildConfigBase {
|
||||
interface BuildConfig {
|
||||
/**
|
||||
* Enable code splitting
|
||||
*/
|
||||
splitting?: boolean;
|
||||
|
||||
/**
|
||||
* List of entrypoints, usually file paths
|
||||
*/
|
||||
@@ -2774,6 +2779,33 @@ declare module "bun" {
|
||||
metafile?: boolean;
|
||||
|
||||
outdir?: string;
|
||||
|
||||
/**
|
||||
* Create a standalone executable
|
||||
*
|
||||
* When `true`, creates an executable for the current platform.
|
||||
* When a target string, creates an executable for that platform.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create executable for current platform
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: {
|
||||
* target: 'linux-x64',
|
||||
* },
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
*
|
||||
* // Cross-compile for Linux x64
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: 'linux-x64',
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
compile?: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
|
||||
}
|
||||
|
||||
interface CompileBuildOptions {
|
||||
@@ -2832,57 +2864,6 @@ declare module "bun" {
|
||||
};
|
||||
}
|
||||
|
||||
// Compile build config - uses outfile for executable output
|
||||
interface CompileBuildConfig extends BuildConfigBase {
|
||||
/**
|
||||
* Create a standalone executable
|
||||
*
|
||||
* When `true`, creates an executable for the current platform.
|
||||
* When a target string, creates an executable for that platform.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create executable for current platform
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: {
|
||||
* target: 'linux-x64',
|
||||
* },
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
*
|
||||
* // Cross-compile for Linux x64
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: 'linux-x64',
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
|
||||
|
||||
/**
|
||||
* Splitting is not currently supported with `.compile`
|
||||
*/
|
||||
splitting?: never;
|
||||
}
|
||||
|
||||
interface NormalBuildConfig extends BuildConfigBase {
|
||||
/**
|
||||
* Enable code splitting
|
||||
*
|
||||
* This does not currently work with {@link CompileBuildConfig.compile `compile`}
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
splitting?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||
*/
|
||||
type BuildConfig = CompileBuildConfig | NormalBuildConfig;
|
||||
|
||||
/**
|
||||
* Hash and verify passwords using argon2 or bcrypt
|
||||
*
|
||||
|
||||
@@ -188,6 +188,103 @@ struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t
|
||||
return loop;
|
||||
}
|
||||
|
||||
/* Shared dispatch loop for both us_loop_run and us_loop_run_bun_tick */
|
||||
static void us_internal_dispatch_ready_polls(struct us_loop_t *loop) {
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Kqueue delivers each filter (READ, WRITE, TIMER, etc.) as a separate kevent,
|
||||
* so the same fd/poll can appear twice in ready_polls. We coalesce them into a
|
||||
* single set of flags per poll before dispatching, matching epoll's behavior
|
||||
* where each fd appears once with a combined bitmask. */
|
||||
struct kevent_flags {
|
||||
uint8_t readable : 1;
|
||||
uint8_t writable : 1;
|
||||
uint8_t error : 1;
|
||||
uint8_t eof : 1;
|
||||
uint8_t skip : 1;
|
||||
uint8_t _pad : 3;
|
||||
};
|
||||
|
||||
_Static_assert(sizeof(struct kevent_flags) == 1, "kevent_flags must be 1 byte");
|
||||
struct kevent_flags coalesced[LIBUS_MAX_READY_POLLS]; /* no zeroing needed — every index is written in the first pass */
|
||||
|
||||
/* First pass: decode kevents and coalesce same-poll entries */
|
||||
for (int i = 0; i < loop->num_ready_polls; i++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, i);
|
||||
if (!poll || CLEAR_POINTER_TAG(poll) != poll) {
|
||||
coalesced[i] = (struct kevent_flags){ .skip = 1 };
|
||||
continue;
|
||||
}
|
||||
|
||||
const int16_t filter = loop->ready_polls[i].filter;
|
||||
const uint16_t flags = loop->ready_polls[i].flags;
|
||||
struct kevent_flags bits = {
|
||||
.readable = (filter == EVFILT_READ || filter == EVFILT_TIMER || filter == EVFILT_MACHPORT),
|
||||
.writable = (filter == EVFILT_WRITE),
|
||||
.error = !!(flags & EV_ERROR),
|
||||
.eof = !!(flags & EV_EOF),
|
||||
};
|
||||
|
||||
/* Look backward for a prior entry with the same poll to coalesce into.
|
||||
* Kqueue returns at most 2 kevents per fd (READ + WRITE). */
|
||||
int merged = 0;
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
if (!coalesced[j].skip && GET_READY_POLL(loop, j) == poll) {
|
||||
coalesced[j].readable |= bits.readable;
|
||||
coalesced[j].writable |= bits.writable;
|
||||
coalesced[j].error |= bits.error;
|
||||
coalesced[j].eof |= bits.eof;
|
||||
coalesced[i] = (struct kevent_flags){ .skip = 1 };
|
||||
merged = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
coalesced[i] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
/* Second pass: dispatch everything in order — tagged pointers and coalesced events */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
if (!poll) continue;
|
||||
|
||||
/* Tagged pointers (FilePoll) go through Bun's own dispatch */
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct kevent_flags bits = coalesced[loop->current_ready_poll];
|
||||
if (bits.skip) continue;
|
||||
|
||||
int events = (bits.readable ? LIBUS_SOCKET_READABLE : 0)
|
||||
| (bits.writable ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
|
||||
events &= us_poll_events(poll);
|
||||
if (events || bits.error || bits.eof) {
|
||||
us_internal_dispatch_ready_poll(poll, bits.error, bits.eof, events);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void us_loop_run(struct us_loop_t *loop) {
|
||||
us_loop_integrate(loop);
|
||||
|
||||
@@ -205,41 +302,7 @@ void us_loop_run(struct us_loop_t *loop) {
|
||||
} while (IS_EINTR(loop->num_ready_polls));
|
||||
#endif
|
||||
|
||||
/* Iterate ready polls, dispatching them by type */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
/* Any ready poll marked with nullptr will be ignored */
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
#else
|
||||
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
|
||||
const int16_t filter = current_kevent->filter;
|
||||
const uint16_t flags = current_kevent->flags;
|
||||
const uint32_t fflags = current_kevent->fflags;
|
||||
|
||||
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
|
||||
// > Instead, the filter will aggregate the events into a single kevent struct
|
||||
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
|
||||
int events = 0
|
||||
| ((filter == EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter == EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
|
||||
const int eof = (flags & (EV_EOF));
|
||||
#endif
|
||||
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
us_internal_dispatch_ready_polls(loop);
|
||||
|
||||
/* Emit post callback */
|
||||
us_internal_loop_post(loop);
|
||||
@@ -263,57 +326,33 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
|
||||
/* Emit pre callback */
|
||||
us_internal_loop_pre(loop);
|
||||
|
||||
|
||||
if (loop->data.jsc_vm)
|
||||
const unsigned int had_wakeups = __atomic_exchange_n(&loop->pending_wakeups, 0, __ATOMIC_ACQUIRE);
|
||||
const int will_idle_inside_event_loop = had_wakeups == 0 && (!timeout || (timeout->tv_nsec != 0 || timeout->tv_sec != 0));
|
||||
if (will_idle_inside_event_loop && loop->data.jsc_vm)
|
||||
Bun__JSC_onBeforeWait(loop->data.jsc_vm);
|
||||
|
||||
/* Fetch ready polls */
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
/* A zero timespec already has a fast path in ep_poll (fs/eventpoll.c):
|
||||
* it sets timed_out=1 (line 1952) and returns before any scheduler
|
||||
* interaction (line 1975). No equivalent of KEVENT_FLAG_IMMEDIATE needed. */
|
||||
loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, timeout);
|
||||
#else
|
||||
do {
|
||||
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout);
|
||||
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024,
|
||||
/* When we won't idle (pending wakeups or zero timeout), use KEVENT_FLAG_IMMEDIATE.
|
||||
* In XNU's kqueue_scan (bsd/kern/kern_event.c):
|
||||
* - KEVENT_FLAG_IMMEDIATE: returns immediately after kqueue_process() (line 8031)
|
||||
* - Zero timespec without the flag: falls through to assert_wait_deadline (line 8039)
|
||||
* and thread_block (line 8048), doing a full context switch cycle (~14us) even
|
||||
* though the deadline is already in the past. */
|
||||
will_idle_inside_event_loop ? 0 : KEVENT_FLAG_IMMEDIATE,
|
||||
timeout);
|
||||
} while (IS_EINTR(loop->num_ready_polls));
|
||||
#endif
|
||||
|
||||
|
||||
/* Iterate ready polls, dispatching them by type */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
/* Any ready poll marked with nullptr will be ignored */
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
#else
|
||||
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
|
||||
const int16_t filter = current_kevent->filter;
|
||||
const uint16_t flags = current_kevent->flags;
|
||||
const uint32_t fflags = current_kevent->fflags;
|
||||
|
||||
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
|
||||
// > Instead, the filter will aggregate the events into a single kevent struct
|
||||
int events = 0
|
||||
| ((filter & EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter & EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
|
||||
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
|
||||
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
|
||||
const int eof = (flags & (EV_EOF));
|
||||
|
||||
#endif
|
||||
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
us_internal_dispatch_ready_polls(loop);
|
||||
|
||||
/* Emit post callback */
|
||||
us_internal_loop_post(loop);
|
||||
@@ -613,7 +652,7 @@ struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int f
|
||||
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
|
||||
cb->loop = loop;
|
||||
cb->cb_expects_the_loop = 1;
|
||||
cb->leave_poll_ready = 0;
|
||||
cb->leave_poll_ready = 1; /* Edge-triggered: skip reading eventfd on wakeup */
|
||||
|
||||
return (struct us_internal_async *) cb;
|
||||
}
|
||||
@@ -635,12 +674,28 @@ void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_int
|
||||
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
|
||||
|
||||
us_poll_start((struct us_poll_t *) a, internal_cb->loop, LIBUS_SOCKET_READABLE);
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
/* Upgrade to edge-triggered to avoid reading the eventfd on each wakeup */
|
||||
struct epoll_event event;
|
||||
event.events = EPOLLIN | EPOLLET;
|
||||
event.data.ptr = (struct us_poll_t *) a;
|
||||
epoll_ctl(internal_cb->loop->fd, EPOLL_CTL_MOD,
|
||||
us_poll_fd((struct us_poll_t *) a), &event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void us_internal_async_wakeup(struct us_internal_async *a) {
|
||||
uint64_t one = 1;
|
||||
int written = write(us_poll_fd((struct us_poll_t *) a), &one, 8);
|
||||
(void)written;
|
||||
int fd = us_poll_fd((struct us_poll_t *) a);
|
||||
uint64_t val;
|
||||
for (val = 1; ; val = 1) {
|
||||
if (write(fd, &val, 8) >= 0) return;
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EAGAIN) {
|
||||
/* Counter overflow — drain and retry */
|
||||
if (read(fd, &val, 8) > 0 || errno == EAGAIN || errno == EINTR) continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ struct us_loop_t {
|
||||
/* Number of polls owned by bun */
|
||||
unsigned int bun_polls;
|
||||
|
||||
/* Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
|
||||
* If non-zero, the event loop will return immediately so we can skip the GC safepoint. */
|
||||
unsigned int pending_wakeups;
|
||||
|
||||
/* The list of ready polls */
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
alignas(LIBUS_EXT_ALIGNMENT) struct epoll_event ready_polls[1024];
|
||||
|
||||
@@ -93,6 +93,9 @@ void us_internal_loop_data_free(struct us_loop_t *loop) {
|
||||
}
|
||||
|
||||
void us_wakeup_loop(struct us_loop_t *loop) {
|
||||
#ifndef LIBUS_USE_LIBUV
|
||||
__atomic_fetch_add(&loop->pending_wakeups, 1, __ATOMIC_RELEASE);
|
||||
#endif
|
||||
us_internal_async_wakeup(loop->data.wakeup_async);
|
||||
}
|
||||
|
||||
@@ -393,8 +396,12 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
if (events & LIBUS_SOCKET_WRITABLE && !error) {
|
||||
s->flags.last_write_failed = 0;
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue is one-shot so is not writable anymore */
|
||||
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0);
|
||||
/* Kqueue EVFILT_WRITE is one-shot so the filter is removed after delivery.
|
||||
* Clear POLLING_OUT to reflect this.
|
||||
* Keep POLLING_IN from the poll's own state, NOT from `events`: kqueue delivers
|
||||
* each filter as a separate kevent, so a pure EVFILT_WRITE event won't have
|
||||
* LIBUS_SOCKET_READABLE set even though the socket is still registered for reads. */
|
||||
p->state.poll_type = us_internal_poll_type(p) | (p->state.poll_type & POLL_TYPE_POLLING_IN);
|
||||
#endif
|
||||
|
||||
s = s->context->on_writable(s);
|
||||
@@ -412,7 +419,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
|
||||
} else {
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue one-shot writable needs to be re-enabled */
|
||||
/* Kqueue one-shot writable needs to be re-registered */
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) | LIBUS_SOCKET_WRITABLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const safety_checks = bun.Environment.isDebug or bun.Environment.enable_asan;
|
||||
|
||||
#heap: *mimalloc.Heap,
|
||||
thread_id: if (safety_checks) std.Thread.Id else void,
|
||||
#heap: if (safety_checks) Owned(*DebugHeap) else *mimalloc.Heap,
|
||||
|
||||
/// Uses the default thread-local heap. This type is zero-sized.
|
||||
///
|
||||
@@ -23,18 +20,18 @@ pub const Default = struct {
|
||||
///
|
||||
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
|
||||
pub const Borrowed = struct {
|
||||
#heap: *mimalloc.Heap,
|
||||
#heap: BorrowedHeap,
|
||||
|
||||
pub fn allocator(self: Borrowed) std.mem.Allocator {
|
||||
return .{ .ptr = self.#heap, .vtable = c_allocator_vtable };
|
||||
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn getDefault() Borrowed {
|
||||
return .{ .#heap = mimalloc.mi_heap_main() };
|
||||
return .{ .#heap = getThreadHeap() };
|
||||
}
|
||||
|
||||
pub fn gc(self: Borrowed) void {
|
||||
mimalloc.mi_heap_collect(self.#heap, false);
|
||||
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
|
||||
}
|
||||
|
||||
pub fn helpCatchMemoryIssues(self: Borrowed) void {
|
||||
@@ -44,17 +41,30 @@ pub const Borrowed = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
|
||||
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
|
||||
}
|
||||
|
||||
fn fromOpaque(ptr: *anyopaque) Borrowed {
|
||||
return .{ .#heap = @ptrCast(@alignCast(ptr)) };
|
||||
}
|
||||
|
||||
fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
|
||||
return if (comptime safety_checks) self.#heap.inner else self.#heap;
|
||||
}
|
||||
|
||||
fn assertThreadLock(self: Borrowed) void {
|
||||
if (comptime safety_checks) self.#heap.thread_lock.assertLocked();
|
||||
}
|
||||
|
||||
fn alignedAlloc(self: Borrowed, len: usize, alignment: Alignment) ?[*]u8 {
|
||||
log("Malloc: {d}\n", .{len});
|
||||
|
||||
const heap = self.getMimallocHeap();
|
||||
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_heap_malloc_aligned(self.#heap, len, alignment.toByteUnits())
|
||||
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
|
||||
else
|
||||
mimalloc.mi_heap_malloc(self.#heap, len);
|
||||
mimalloc.mi_heap_malloc(heap, len);
|
||||
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
const usable = mimalloc.mi_malloc_usable_size(ptr);
|
||||
@@ -79,17 +89,42 @@ pub const Borrowed = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const BorrowedHeap = if (safety_checks) *DebugHeap else *mimalloc.Heap;
|
||||
|
||||
const DebugHeap = struct {
|
||||
inner: *mimalloc.Heap,
|
||||
thread_lock: bun.safety.ThreadLock,
|
||||
|
||||
pub const deinit = void;
|
||||
};
|
||||
|
||||
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
|
||||
|
||||
fn getThreadHeap() BorrowedHeap {
|
||||
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
|
||||
if (thread_heap == null) {
|
||||
thread_heap = .{
|
||||
.inner = mimalloc.mi_heap_get_default(),
|
||||
.thread_lock = .initLocked(),
|
||||
};
|
||||
}
|
||||
return &thread_heap.?;
|
||||
}
|
||||
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
|
||||
pub fn allocator(self: Self) std.mem.Allocator {
|
||||
self.assertThreadOwnership();
|
||||
return self.borrow().allocator();
|
||||
}
|
||||
|
||||
pub fn borrow(self: Self) Borrowed {
|
||||
return .{ .#heap = self.#heap };
|
||||
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
|
||||
}
|
||||
|
||||
/// Internally, mimalloc calls mi_heap_get_default()
|
||||
/// to get the default heap.
|
||||
/// It uses pthread_getspecific to do that.
|
||||
/// We can save those extra calls if we just do it once in here
|
||||
pub fn getThreadLocalDefault() std.mem.Allocator {
|
||||
if (bun.Environment.enable_asan) return bun.default_allocator;
|
||||
return Borrowed.getDefault().allocator();
|
||||
@@ -122,15 +157,22 @@ pub fn dumpStats(_: Self) void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
mimalloc.mi_heap_destroy(self.#heap);
|
||||
const mimalloc_heap = self.borrow().getMimallocHeap();
|
||||
if (comptime safety_checks) {
|
||||
self.#heap.deinit();
|
||||
}
|
||||
mimalloc.mi_heap_destroy(mimalloc_heap);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn init() Self {
|
||||
return .{
|
||||
.#heap = mimalloc.mi_heap_new() orelse bun.outOfMemory(),
|
||||
.thread_id = if (safety_checks) std.Thread.getCurrentId() else {},
|
||||
};
|
||||
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
|
||||
if (comptime !safety_checks) return .{ .#heap = mimalloc_heap };
|
||||
const heap: Owned(*DebugHeap) = .new(.{
|
||||
.inner = mimalloc_heap,
|
||||
.thread_lock = .initLocked(),
|
||||
});
|
||||
return .{ .#heap = heap };
|
||||
}
|
||||
|
||||
pub fn gc(self: Self) void {
|
||||
@@ -141,16 +183,8 @@ pub fn helpCatchMemoryIssues(self: Self) void {
|
||||
self.borrow().helpCatchMemoryIssues();
|
||||
}
|
||||
|
||||
fn assertThreadOwnership(self: Self) void {
|
||||
if (comptime safety_checks) {
|
||||
const current_thread = std.Thread.getCurrentId();
|
||||
if (current_thread != self.thread_id) {
|
||||
std.debug.panic(
|
||||
"MimallocArena used from wrong thread: arena belongs to thread {d}, but current thread is {d}",
|
||||
.{ self.thread_id, current_thread },
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
|
||||
return self.borrow().ownsPtr(ptr);
|
||||
}
|
||||
|
||||
fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
@@ -159,10 +193,13 @@ fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
|
||||
fn vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return self.alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
fn vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
@@ -186,17 +223,39 @@ fn vtable_free(
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to expand or shrink memory, allowing relocation.
|
||||
///
|
||||
/// `memory.len` must equal the length requested from the most recent
|
||||
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
|
||||
/// equal the same value that was passed as the `alignment` parameter to
|
||||
/// the original `alloc` call.
|
||||
///
|
||||
/// A non-`null` return value indicates the resize was successful. The
|
||||
/// allocation may have same address, or may have been relocated. In either
|
||||
/// case, the allocation now has size of `new_len`. A `null` return value
|
||||
/// indicates that the resize would be equivalent to allocating new memory,
|
||||
/// copying the bytes from the old memory, and then freeing the old memory.
|
||||
/// In such case, it is more efficient for the caller to perform the copy.
|
||||
///
|
||||
/// `new_len` must be greater than zero.
|
||||
///
|
||||
/// `ret_addr` is optionally provided as the first return address of the
|
||||
/// allocation call stack. If the value is `0` it means no return address
|
||||
/// has been provided.
|
||||
fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
const value = mimalloc.mi_heap_realloc_aligned(self.#heap, buf.ptr, new_len, alignment.toByteUnits());
|
||||
self.assertThreadLock();
|
||||
const heap = self.getMimallocHeap();
|
||||
const aligned_size = alignment.toByteUnits();
|
||||
const value = mimalloc.mi_heap_realloc_aligned(heap, buf.ptr, new_len, aligned_size);
|
||||
return @ptrCast(value);
|
||||
}
|
||||
|
||||
pub fn isInstance(alloc: std.mem.Allocator) bool {
|
||||
return alloc.vtable == c_allocator_vtable;
|
||||
return alloc.vtable == &c_allocator_vtable;
|
||||
}
|
||||
|
||||
const c_allocator_vtable = &std.mem.Allocator.VTable{
|
||||
const c_allocator_vtable = std.mem.Allocator.VTable{
|
||||
.alloc = vtable_alloc,
|
||||
.resize = vtable_resize,
|
||||
.remap = vtable_remap,
|
||||
@@ -209,3 +268,5 @@ const Alignment = std.mem.Alignment;
|
||||
const bun = @import("bun");
|
||||
const assert = bun.assert;
|
||||
const mimalloc = bun.mimalloc;
|
||||
const Owned = bun.ptr.Owned;
|
||||
const safety_checks = bun.Environment.ci_assert;
|
||||
|
||||
@@ -60,29 +60,17 @@ pub const Heap = opaque {
|
||||
return mi_heap_realloc(self, p, newsize);
|
||||
}
|
||||
|
||||
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
|
||||
return mi_heap_contains(self, p);
|
||||
pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
|
||||
return mi_heap_check_owned(self, p);
|
||||
}
|
||||
};
|
||||
pub extern fn mi_heap_new() ?*Heap;
|
||||
pub extern fn mi_heap_delete(heap: *Heap) void;
|
||||
pub extern fn mi_heap_destroy(heap: *Heap) void;
|
||||
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
|
||||
pub extern fn mi_heap_get_default() *Heap;
|
||||
pub extern fn mi_heap_get_backing() *Heap;
|
||||
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
|
||||
pub extern fn mi_heap_main() *Heap;
|
||||
|
||||
// Thread-local heap (theap) API - new in mimalloc v3
|
||||
pub const THeap = opaque {};
|
||||
pub extern fn mi_theap_get_default() *THeap;
|
||||
pub extern fn mi_theap_set_default(theap: *THeap) *THeap;
|
||||
pub extern fn mi_theap_collect(theap: *THeap, force: bool) void;
|
||||
pub extern fn mi_theap_malloc(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_zalloc(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_calloc(theap: *THeap, count: usize, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_malloc_small(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_malloc_aligned(theap: *THeap, size: usize, alignment: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_realloc(theap: *THeap, p: ?*anyopaque, newsize: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_destroy(theap: *THeap) void;
|
||||
pub extern fn mi_heap_theap(heap: *Heap) *THeap;
|
||||
pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_calloc(heap: *Heap, count: usize, size: usize) ?*anyopaque;
|
||||
@@ -114,7 +102,8 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
|
||||
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
|
||||
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
|
||||
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
|
||||
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
|
||||
pub const struct_mi_heap_area_s = extern struct {
|
||||
blocks: ?*anyopaque,
|
||||
|
||||
@@ -245,6 +245,16 @@ pub const All = struct {
|
||||
}
|
||||
|
||||
pub fn getTimeout(this: *All, spec: *timespec, vm: *VirtualMachine) bool {
|
||||
// On POSIX, if there are pending immediate tasks, use a zero timeout
|
||||
// so epoll/kqueue returns immediately without the overhead of writing
|
||||
// to the eventfd via wakeup().
|
||||
if (comptime Environment.isPosix) {
|
||||
if (vm.event_loop.immediate_tasks.items.len > 0) {
|
||||
spec.* = .{ .nsec = 0, .sec = 0 };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var maybe_now: ?timespec = null;
|
||||
while (this.timers.peek()) |min| {
|
||||
const now = maybe_now orelse now: {
|
||||
|
||||
@@ -335,6 +335,12 @@ static JSValue constructBunSQLObject(VM& vm, JSObject* bunObject)
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue JSPasswordObject__create(JSGlobalObject*);
|
||||
extern "C" JSC::EncodedJSValue Bun__createYogaModule(Zig::GlobalObject*);
|
||||
|
||||
static JSValue constructYogaObject(VM& vm, JSObject* bunObject)
|
||||
{
|
||||
return JSValue::decode(Bun__createYogaModule(jsCast<Zig::GlobalObject*>(bunObject->globalObject())));
|
||||
}
|
||||
|
||||
static JSValue constructPasswordObject(VM& vm, JSObject* bunObject)
|
||||
{
|
||||
@@ -924,6 +930,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
markdown BunObject_lazyPropCb_wrap_markdown DontDelete|PropertyCallback
|
||||
TOML BunObject_lazyPropCb_wrap_TOML DontDelete|PropertyCallback
|
||||
YAML BunObject_lazyPropCb_wrap_YAML DontDelete|PropertyCallback
|
||||
Yoga constructYogaObject ReadOnly|DontDelete|PropertyCallback
|
||||
Transpiler BunObject_lazyPropCb_wrap_Transpiler DontDelete|PropertyCallback
|
||||
embeddedFiles BunObject_lazyPropCb_wrap_embeddedFiles DontDelete|PropertyCallback
|
||||
S3Client BunObject_lazyPropCb_wrap_S3Client DontDelete|PropertyCallback
|
||||
|
||||
@@ -119,6 +119,7 @@ JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap16);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap32);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_toString);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_slice);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64LE);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64BE);
|
||||
@@ -1879,6 +1880,103 @@ bool inline parseArrayIndex(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalO
|
||||
return true;
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE size_t adjustSliceOffsetInt32(int32_t offset, size_t length)
|
||||
{
|
||||
if (offset < 0) {
|
||||
int64_t adjusted = static_cast<int64_t>(offset) + static_cast<int64_t>(length);
|
||||
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
|
||||
}
|
||||
return static_cast<size_t>(offset) < length ? static_cast<size_t>(offset) : length;
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE size_t adjustSliceOffsetDouble(double offset, size_t length)
|
||||
{
|
||||
if (std::isnan(offset)) {
|
||||
return 0;
|
||||
}
|
||||
offset = std::trunc(offset);
|
||||
if (offset == 0) {
|
||||
return 0;
|
||||
} else if (offset < 0) {
|
||||
double adjusted = offset + static_cast<double>(length);
|
||||
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
|
||||
} else {
|
||||
return offset < static_cast<double>(length) ? static_cast<size_t>(offset) : length;
|
||||
}
|
||||
}
|
||||
|
||||
static JSC::EncodedJSValue jsBufferPrototypeFunction_sliceBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
|
||||
size_t byteLength = castedThis->byteLength();
|
||||
size_t byteOffset = castedThis->byteOffset();
|
||||
|
||||
size_t startOffset = 0;
|
||||
size_t endOffset = byteLength;
|
||||
|
||||
unsigned argCount = callFrame->argumentCount();
|
||||
|
||||
if (argCount > 0) {
|
||||
JSValue startArg = callFrame->uncheckedArgument(0);
|
||||
if (startArg.isInt32()) {
|
||||
startOffset = adjustSliceOffsetInt32(startArg.asInt32(), byteLength);
|
||||
} else if (!startArg.isUndefined()) {
|
||||
double startD = startArg.toNumber(lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
startOffset = adjustSliceOffsetDouble(startD, byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (argCount > 1) {
|
||||
JSValue endArg = callFrame->uncheckedArgument(1);
|
||||
if (endArg.isInt32()) {
|
||||
endOffset = adjustSliceOffsetInt32(endArg.asInt32(), byteLength);
|
||||
} else if (!endArg.isUndefined()) {
|
||||
double endD = endArg.toNumber(lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
endOffset = adjustSliceOffsetDouble(endD, byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
size_t newLength = endOffset > startOffset ? endOffset - startOffset : 0;
|
||||
|
||||
if (castedThis->isDetached()) [[unlikely]] {
|
||||
throwVMTypeError(lexicalGlobalObject, throwScope, "Buffer is detached"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<ArrayBuffer> buffer = castedThis->possiblySharedBuffer();
|
||||
if (!buffer) {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (castedThis->isResizableOrGrowableShared()) {
|
||||
auto* subclassStructure = globalObject->JSResizableOrGrowableSharedBufferSubclassStructure();
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
|
||||
}
|
||||
|
||||
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L834
|
||||
// using byteLength and byte offsets here is intentional
|
||||
static JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
|
||||
@@ -2430,6 +2528,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64, (JSGlobalObject * lex
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_swap64Body>(*lexicalGlobalObject, *callFrame, "swap64");
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_slice, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_sliceBody>(*lexicalGlobalObject, *callFrame, "slice");
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_toString, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString");
|
||||
@@ -2711,8 +2814,8 @@ static const HashTableValue JSBufferPrototypeTableValues[]
|
||||
{ "readUIntBE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } },
|
||||
{ "readUIntLE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } },
|
||||
|
||||
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
|
||||
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
|
||||
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
|
||||
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
|
||||
{ "swap16"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap16, 0 } },
|
||||
{ "swap32"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap32, 0 } },
|
||||
{ "swap64"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap64, 0 } },
|
||||
|
||||
117
src/bun.js/bindings/JSYogaConfig.cpp
Normal file
117
src/bun.js/bindings/JSYogaConfig.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include "YogaConfigImpl.h"
|
||||
#include "webcore/DOMIsoSubspaces.h"
|
||||
#include "webcore/DOMClientIsoSubspaces.h"
|
||||
#include "webcore/WebCoreJSClientData.h"
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
const JSC::ClassInfo JSYogaConfig::s_info = { "Config"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConfig) };
|
||||
|
||||
JSYogaConfig::JSYogaConfig(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
, m_impl(YogaConfigImpl::create())
|
||||
{
|
||||
}
|
||||
|
||||
JSYogaConfig::JSYogaConfig(JSC::VM& vm, JSC::Structure* structure, Ref<YogaConfigImpl>&& impl)
|
||||
: Base(vm, structure)
|
||||
, m_impl(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
JSYogaConfig::~JSYogaConfig()
|
||||
{
|
||||
// The WeakHandleOwner::finalize should handle cleanup
|
||||
// Don't interfere with that mechanism
|
||||
}
|
||||
|
||||
JSYogaConfig* JSYogaConfig::create(JSC::VM& vm, JSC::Structure* structure)
|
||||
{
|
||||
JSYogaConfig* config = new (NotNull, JSC::allocateCell<JSYogaConfig>(vm)) JSYogaConfig(vm, structure);
|
||||
config->finishCreation(vm);
|
||||
return config;
|
||||
}
|
||||
|
||||
JSYogaConfig* JSYogaConfig::create(JSC::VM& vm, JSC::Structure* structure, Ref<YogaConfigImpl>&& impl)
|
||||
{
|
||||
JSYogaConfig* config = new (NotNull, JSC::allocateCell<JSYogaConfig>(vm)) JSYogaConfig(vm, structure, std::move(impl));
|
||||
config->finishCreation(vm);
|
||||
return config;
|
||||
}
|
||||
|
||||
void JSYogaConfig::finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Set this JS wrapper in the C++ impl
|
||||
m_impl->setJSWrapper(this);
|
||||
}
|
||||
|
||||
JSC::Structure* JSYogaConfig::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
void JSYogaConfig::destroy(JSC::JSCell* cell)
|
||||
{
|
||||
static_cast<JSYogaConfig*>(cell)->~JSYogaConfig();
|
||||
}
|
||||
|
||||
template<typename MyClassT, JSC::SubspaceAccess mode>
|
||||
JSC::GCClient::IsoSubspace* JSYogaConfig::subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForJSYogaConfig.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSYogaConfig = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForJSYogaConfig.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSYogaConfig = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaConfig::visitChildrenImpl(JSC::JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
JSYogaConfig* thisObject = jsCast<JSYogaConfig*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
visitor.append(thisObject->m_context);
|
||||
visitor.append(thisObject->m_loggerFunc);
|
||||
visitor.append(thisObject->m_cloneNodeFunc);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSYogaConfig);
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaConfig::visitAdditionalChildren(Visitor& visitor)
|
||||
{
|
||||
visitor.append(m_context);
|
||||
visitor.append(m_loggerFunc);
|
||||
visitor.append(m_cloneNodeFunc);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSYogaConfig);
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaConfig::visitOutputConstraints(JSC::JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* thisObject = jsCast<JSYogaConfig*>(cell);
|
||||
|
||||
// Lock for concurrent GC thread safety
|
||||
WTF::Locker locker { thisObject->cellLock() };
|
||||
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitOutputConstraints(thisObject, visitor);
|
||||
thisObject->visitAdditionalChildren(visitor);
|
||||
}
|
||||
|
||||
template void JSYogaConfig::visitOutputConstraints(JSC::JSCell*, JSC::AbstractSlotVisitor&);
|
||||
template void JSYogaConfig::visitOutputConstraints(JSC::JSCell*, JSC::SlotVisitor&);
|
||||
|
||||
} // namespace Bun
|
||||
56
src/bun.js/bindings/JSYogaConfig.h
Normal file
56
src/bun.js/bindings/JSYogaConfig.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <memory>
|
||||
#include <JavaScriptCore/JSDestructibleObject.h>
|
||||
#include <JavaScriptCore/WriteBarrier.h>
|
||||
#include <wtf/Ref.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct YGConfig* YGConfigRef;
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class YogaConfigImpl;
|
||||
|
||||
class JSYogaConfig final : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
static constexpr JSC::DestructionMode needsDestruction = JSC::NeedsDestruction;
|
||||
|
||||
static JSYogaConfig* create(JSC::VM&, JSC::Structure*);
|
||||
static JSYogaConfig* create(JSC::VM&, JSC::Structure*, Ref<YogaConfigImpl>&&);
|
||||
static void destroy(JSC::JSCell*);
|
||||
static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue);
|
||||
~JSYogaConfig();
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM&);
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
template<typename Visitor> void visitAdditionalChildren(Visitor&);
|
||||
template<typename Visitor> static void visitOutputConstraints(JSC::JSCell*, Visitor&);
|
||||
|
||||
YogaConfigImpl& impl() { return m_impl.get(); }
|
||||
const YogaConfigImpl& impl() const { return m_impl.get(); }
|
||||
|
||||
// Context storage
|
||||
JSC::WriteBarrier<JSC::Unknown> m_context;
|
||||
|
||||
// Logger callback
|
||||
JSC::WriteBarrier<JSC::JSObject> m_loggerFunc;
|
||||
|
||||
// Clone node callback
|
||||
JSC::WriteBarrier<JSC::JSObject> m_cloneNodeFunc;
|
||||
|
||||
private:
|
||||
JSYogaConfig(JSC::VM&, JSC::Structure*);
|
||||
JSYogaConfig(JSC::VM&, JSC::Structure*, Ref<YogaConfigImpl>&&);
|
||||
void finishCreation(JSC::VM&);
|
||||
|
||||
Ref<YogaConfigImpl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
39
src/bun.js/bindings/JSYogaConfigOwner.cpp
Normal file
39
src/bun.js/bindings/JSYogaConfigOwner.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "JSYogaConfigOwner.h"
|
||||
#include "YogaConfigImpl.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <wtf/NeverDestroyed.h>
|
||||
#include <wtf/Compiler.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
void JSYogaConfigOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
|
||||
{
|
||||
// This is where we deref the C++ YogaConfigImpl wrapper
|
||||
// The context contains our YogaConfigImpl
|
||||
auto* impl = static_cast<YogaConfigImpl*>(context);
|
||||
|
||||
// Deref the YogaConfigImpl - this will decrease its reference count
|
||||
// and potentially destroy it if no other references exist
|
||||
impl->deref();
|
||||
}
|
||||
|
||||
bool JSYogaConfigOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, JSC::AbstractSlotVisitor& visitor, ASCIILiteral* reason)
|
||||
{
|
||||
UNUSED_PARAM(handle);
|
||||
UNUSED_PARAM(context);
|
||||
// YogaConfig doesn't currently use opaque roots, so always return false
|
||||
// This allows normal GC collection based on JS reference reachability
|
||||
if (reason)
|
||||
*reason = "YogaConfig not using opaque roots"_s;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JSYogaConfigOwner& jsYogaConfigOwner()
|
||||
{
|
||||
static NeverDestroyed<JSYogaConfigOwner> owner;
|
||||
return owner.get();
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
20
src/bun.js/bindings/JSYogaConfigOwner.h
Normal file
20
src/bun.js/bindings/JSYogaConfigOwner.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/WeakHandleOwner.h>
|
||||
#include <JavaScriptCore/Weak.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class YogaConfigImpl;
|
||||
class JSYogaConfig;
|
||||
|
||||
class JSYogaConfigOwner : public JSC::WeakHandleOwner {
|
||||
public:
|
||||
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
|
||||
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
|
||||
};
|
||||
|
||||
JSYogaConfigOwner& jsYogaConfigOwner();
|
||||
|
||||
} // namespace Bun
|
||||
121
src/bun.js/bindings/JSYogaConstants.cpp
Normal file
121
src/bun.js/bindings/JSYogaConstants.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaConstants.h"
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
const JSC::ClassInfo JSYogaConstants::s_info = { "YogaConstants"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConstants) };
|
||||
|
||||
void JSYogaConstants::finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Align values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_AUTO"_s), JSC::jsNumber(static_cast<int>(YGAlignAuto)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_FLEX_START"_s), JSC::jsNumber(static_cast<int>(YGAlignFlexStart)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_CENTER"_s), JSC::jsNumber(static_cast<int>(YGAlignCenter)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_FLEX_END"_s), JSC::jsNumber(static_cast<int>(YGAlignFlexEnd)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_STRETCH"_s), JSC::jsNumber(static_cast<int>(YGAlignStretch)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_BASELINE"_s), JSC::jsNumber(static_cast<int>(YGAlignBaseline)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_BETWEEN"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceBetween)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_AROUND"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceAround)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_EVENLY"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceEvenly)), 0);
|
||||
|
||||
// Direction values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_INHERIT"_s), JSC::jsNumber(static_cast<int>(YGDirectionInherit)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_LTR"_s), JSC::jsNumber(static_cast<int>(YGDirectionLTR)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_RTL"_s), JSC::jsNumber(static_cast<int>(YGDirectionRTL)), 0);
|
||||
|
||||
// Display values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_FLEX"_s), JSC::jsNumber(static_cast<int>(YGDisplayFlex)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_NONE"_s), JSC::jsNumber(static_cast<int>(YGDisplayNone)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_CONTENTS"_s), JSC::jsNumber(static_cast<int>(YGDisplayContents)), 0);
|
||||
|
||||
// Edge values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_LEFT"_s), JSC::jsNumber(static_cast<int>(YGEdgeLeft)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_TOP"_s), JSC::jsNumber(static_cast<int>(YGEdgeTop)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_RIGHT"_s), JSC::jsNumber(static_cast<int>(YGEdgeRight)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_BOTTOM"_s), JSC::jsNumber(static_cast<int>(YGEdgeBottom)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_START"_s), JSC::jsNumber(static_cast<int>(YGEdgeStart)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_END"_s), JSC::jsNumber(static_cast<int>(YGEdgeEnd)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_HORIZONTAL"_s), JSC::jsNumber(static_cast<int>(YGEdgeHorizontal)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_VERTICAL"_s), JSC::jsNumber(static_cast<int>(YGEdgeVertical)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_ALL"_s), JSC::jsNumber(static_cast<int>(YGEdgeAll)), 0);
|
||||
|
||||
// Experimental feature values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS"_s), JSC::jsNumber(static_cast<int>(YGExperimentalFeatureWebFlexBasis)), 0);
|
||||
|
||||
// Flex direction values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_COLUMN"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionColumn)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_COLUMN_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionColumnReverse)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_ROW"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionRow)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_ROW_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionRowReverse)), 0);
|
||||
|
||||
// Gutter values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_COLUMN"_s), JSC::jsNumber(static_cast<int>(YGGutterColumn)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_ROW"_s), JSC::jsNumber(static_cast<int>(YGGutterRow)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_ALL"_s), JSC::jsNumber(static_cast<int>(YGGutterAll)), 0);
|
||||
|
||||
// Justify values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_FLEX_START"_s), JSC::jsNumber(static_cast<int>(YGJustifyFlexStart)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_CENTER"_s), JSC::jsNumber(static_cast<int>(YGJustifyCenter)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_FLEX_END"_s), JSC::jsNumber(static_cast<int>(YGJustifyFlexEnd)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_BETWEEN"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceBetween)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_AROUND"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceAround)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_EVENLY"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceEvenly)), 0);
|
||||
|
||||
// Log level values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_ERROR"_s), JSC::jsNumber(static_cast<int>(YGLogLevelError)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_WARN"_s), JSC::jsNumber(static_cast<int>(YGLogLevelWarn)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_INFO"_s), JSC::jsNumber(static_cast<int>(YGLogLevelInfo)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_DEBUG"_s), JSC::jsNumber(static_cast<int>(YGLogLevelDebug)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_VERBOSE"_s), JSC::jsNumber(static_cast<int>(YGLogLevelVerbose)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_FATAL"_s), JSC::jsNumber(static_cast<int>(YGLogLevelFatal)), 0);
|
||||
|
||||
// Measure mode values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_UNDEFINED"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeUndefined)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_EXACTLY"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeExactly)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_AT_MOST"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeAtMost)), 0);
|
||||
|
||||
// Node type values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "NODE_TYPE_DEFAULT"_s), JSC::jsNumber(static_cast<int>(YGNodeTypeDefault)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "NODE_TYPE_TEXT"_s), JSC::jsNumber(static_cast<int>(YGNodeTypeText)), 0);
|
||||
|
||||
// Overflow values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_VISIBLE"_s), JSC::jsNumber(static_cast<int>(YGOverflowVisible)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_HIDDEN"_s), JSC::jsNumber(static_cast<int>(YGOverflowHidden)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_SCROLL"_s), JSC::jsNumber(static_cast<int>(YGOverflowScroll)), 0);
|
||||
|
||||
// Position type values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_STATIC"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeStatic)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_RELATIVE"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeRelative)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_ABSOLUTE"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeAbsolute)), 0);
|
||||
|
||||
// Unit values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_UNDEFINED"_s), JSC::jsNumber(static_cast<int>(YGUnitUndefined)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_POINT"_s), JSC::jsNumber(static_cast<int>(YGUnitPoint)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_PERCENT"_s), JSC::jsNumber(static_cast<int>(YGUnitPercent)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_AUTO"_s), JSC::jsNumber(static_cast<int>(YGUnitAuto)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_MAX_CONTENT"_s), JSC::jsNumber(static_cast<int>(YGUnitMaxContent)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_FIT_CONTENT"_s), JSC::jsNumber(static_cast<int>(YGUnitFitContent)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_STRETCH"_s), JSC::jsNumber(static_cast<int>(YGUnitStretch)), 0);
|
||||
|
||||
// Wrap values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_NO_WRAP"_s), JSC::jsNumber(static_cast<int>(YGWrapNoWrap)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_WRAP"_s), JSC::jsNumber(static_cast<int>(YGWrapWrap)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_WRAP_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGWrapWrapReverse)), 0);
|
||||
|
||||
// Errata values
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_NONE"_s), JSC::jsNumber(static_cast<int>(YGErrataNone)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_STRETCH_FLEX_BASIS"_s), JSC::jsNumber(static_cast<int>(YGErrataStretchFlexBasis)), 0);
|
||||
// YGErrataAbsolutePositioningIncorrect is not available in this version of Yoga
|
||||
// putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ABSOLUTE_POSITIONING_INCORRECT"_s), JSC::jsNumber(static_cast<int>(YGErrataAbsolutePositioningIncorrect)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE"_s), JSC::jsNumber(static_cast<int>(YGErrataAbsolutePercentAgainstInnerSize)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ALL"_s), JSC::jsNumber(static_cast<int>(YGErrataAll)), 0);
|
||||
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_CLASSIC"_s), JSC::jsNumber(static_cast<int>(YGErrataClassic)), 0);
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
41
src/bun.js/bindings/JSYogaConstants.h
Normal file
41
src/bun.js/bindings/JSYogaConstants.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaConstants final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaConstants* create(JSC::VM& vm, JSC::Structure* structure)
|
||||
{
|
||||
JSYogaConstants* constants = new (NotNull, allocateCell<JSYogaConstants>(vm)) JSYogaConstants(vm, structure);
|
||||
constants->finishCreation(vm);
|
||||
return constants;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaConstants(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
229
src/bun.js/bindings/JSYogaConstructor.cpp
Normal file
229
src/bun.js/bindings/JSYogaConstructor.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaConstructor.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include "YogaConfigImpl.h"
|
||||
#include "JSYogaNode.h"
|
||||
#include "JSYogaModule.h"
|
||||
#include "JSYogaPrototype.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
#ifndef UNLIKELY
|
||||
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
#endif
|
||||
|
||||
namespace Bun {
|
||||
|
||||
// Forward declarations for constructor functions
|
||||
static JSC_DECLARE_HOST_FUNCTION(constructJSYogaConfig);
|
||||
static JSC_DECLARE_HOST_FUNCTION(callJSYogaConfig);
|
||||
static JSC_DECLARE_HOST_FUNCTION(constructJSYogaNode);
|
||||
static JSC_DECLARE_HOST_FUNCTION(callJSYogaNode);
|
||||
static JSC_DECLARE_HOST_FUNCTION(createJSYogaConfig);
|
||||
static JSC_DECLARE_HOST_FUNCTION(createJSYogaNode);
|
||||
|
||||
// Config Constructor implementation
|
||||
const JSC::ClassInfo JSYogaConfigConstructor::s_info = { "Config"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConfigConstructor) };
|
||||
|
||||
JSYogaConfigConstructor::JSYogaConfigConstructor(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure, callJSYogaConfig, constructJSYogaConfig)
|
||||
{
|
||||
}
|
||||
|
||||
void JSYogaConfigConstructor::finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
|
||||
{
|
||||
Base::finishCreation(vm, 0, "Config"_s, PropertyAdditionMode::WithStructureTransition);
|
||||
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
|
||||
// Add static methods - create() is a factory function that doesn't require 'new'
|
||||
putDirectNativeFunction(vm, this->globalObject(), JSC::Identifier::fromString(vm, "create"_s), 0, createJSYogaConfig, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
}
|
||||
|
||||
// Node Constructor implementation
|
||||
const JSC::ClassInfo JSYogaNodeConstructor::s_info = { "Node"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaNodeConstructor) };
|
||||
|
||||
JSYogaNodeConstructor::JSYogaNodeConstructor(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure, callJSYogaNode, constructJSYogaNode)
|
||||
{
|
||||
}
|
||||
|
||||
void JSYogaNodeConstructor::finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
|
||||
{
|
||||
Base::finishCreation(vm, 1, "Node"_s, PropertyAdditionMode::WithStructureTransition); // 1 for optional config parameter
|
||||
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
|
||||
// Add static methods - create() is a factory function that doesn't require 'new'
|
||||
putDirectNativeFunction(vm, this->globalObject(), JSC::Identifier::fromString(vm, "create"_s), 1, createJSYogaNode, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
}
|
||||
|
||||
// Constructor functions
|
||||
JSC_DEFINE_HOST_FUNCTION(constructJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
JSC::Structure* structure = zigGlobalObject->m_JSYogaConfigClassStructure.get(zigGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// Handle subclassing
|
||||
JSC::JSValue newTarget = callFrame->newTarget();
|
||||
if (UNLIKELY(zigGlobalObject->m_JSYogaConfigClassStructure.constructor(zigGlobalObject) != newTarget)) {
|
||||
if (!newTarget) {
|
||||
throwTypeError(globalObject, scope, "Class constructor Config cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
structure = JSC::InternalFunction::createSubclassStructure(
|
||||
globalObject, newTarget.getObject(), functionGlobalObject->m_JSYogaConfigClassStructure.get(functionGlobalObject));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSYogaConfig::create(vm, structure)));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(callJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
throwTypeError(globalObject, scope, "Class constructor Config cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(constructJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
JSC::Structure* structure = zigGlobalObject->m_JSYogaNodeClassStructure.get(zigGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// Handle subclassing
|
||||
JSC::JSValue newTarget = callFrame->newTarget();
|
||||
if (UNLIKELY(zigGlobalObject->m_JSYogaNodeClassStructure.constructor(zigGlobalObject) != newTarget)) {
|
||||
if (!newTarget) {
|
||||
throwTypeError(globalObject, scope, "Class constructor Node cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
structure = JSC::InternalFunction::createSubclassStructure(
|
||||
globalObject, newTarget.getObject(), functionGlobalObject->m_JSYogaNodeClassStructure.get(functionGlobalObject));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
|
||||
// Optional config parameter
|
||||
YGConfigRef config = nullptr;
|
||||
JSYogaConfig* jsConfig = nullptr;
|
||||
if (callFrame->argumentCount() > 0) {
|
||||
JSC::JSValue configArg = callFrame->uncheckedArgument(0);
|
||||
if (!configArg.isUndefinedOrNull()) {
|
||||
jsConfig = JSC::jsDynamicCast<JSYogaConfig*>(configArg);
|
||||
if (!jsConfig) {
|
||||
throwTypeError(globalObject, scope, "First argument must be a Yoga.Config instance"_s);
|
||||
return {};
|
||||
}
|
||||
config = jsConfig->impl().yogaConfig();
|
||||
}
|
||||
}
|
||||
|
||||
auto* node = JSYogaNode::create(vm, globalObject, structure, config, jsConfig);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
return JSC::JSValue::encode(node);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(callJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
throwTypeError(globalObject, scope, "Class constructor Node cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Factory functions that don't require 'new'
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(createJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
JSC::Structure* structure = zigGlobalObject->m_JSYogaConfigClassStructure.get(zigGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSYogaConfig::create(vm, structure)));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(createJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
JSC::Structure* structure = zigGlobalObject->m_JSYogaNodeClassStructure.get(zigGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// Optional config parameter
|
||||
YGConfigRef config = nullptr;
|
||||
JSYogaConfig* jsConfig = nullptr;
|
||||
if (callFrame->argumentCount() > 0) {
|
||||
JSC::JSValue configArg = callFrame->uncheckedArgument(0);
|
||||
if (!configArg.isUndefinedOrNull()) {
|
||||
jsConfig = JSC::jsDynamicCast<JSYogaConfig*>(configArg);
|
||||
if (!jsConfig) {
|
||||
throwTypeError(globalObject, scope, "First argument must be a Yoga.Config instance"_s);
|
||||
return {};
|
||||
}
|
||||
config = jsConfig->impl().yogaConfig();
|
||||
}
|
||||
}
|
||||
|
||||
auto* node = JSYogaNode::create(vm, globalObject, structure, config, jsConfig);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
return JSC::JSValue::encode(node);
|
||||
}
|
||||
|
||||
// Setup functions for lazy initialization
|
||||
void setupJSYogaConfigClassStructure(JSC::LazyClassStructure::Initializer& init)
|
||||
{
|
||||
auto* prototypeStructure = JSYogaConfigPrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
|
||||
auto* prototype = JSYogaConfigPrototype::create(init.vm, init.global, prototypeStructure);
|
||||
|
||||
auto* constructorStructure = JSYogaConfigConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
|
||||
auto* constructor = JSYogaConfigConstructor::create(init.vm, constructorStructure, prototype);
|
||||
|
||||
auto* structure = JSYogaConfig::createStructure(init.vm, init.global, prototype);
|
||||
|
||||
init.setPrototype(prototype);
|
||||
init.setStructure(structure);
|
||||
init.setConstructor(constructor);
|
||||
}
|
||||
|
||||
void setupJSYogaNodeClassStructure(JSC::LazyClassStructure::Initializer& init)
|
||||
{
|
||||
auto* prototypeStructure = JSYogaNodePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
|
||||
auto* prototype = JSYogaNodePrototype::create(init.vm, init.global, prototypeStructure);
|
||||
|
||||
auto* constructorStructure = JSYogaNodeConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
|
||||
auto* constructor = JSYogaNodeConstructor::create(init.vm, constructorStructure, prototype);
|
||||
|
||||
auto* structure = JSYogaNode::createStructure(init.vm, init.global, prototype);
|
||||
|
||||
init.setPrototype(prototype);
|
||||
init.setStructure(structure);
|
||||
init.setConstructor(constructor);
|
||||
}
|
||||
|
||||
void setupJSYogaModuleClassStructure(JSC::LazyClassStructure::Initializer& init)
|
||||
{
|
||||
auto* structure = JSYogaModule::createStructure(init.vm, init.global, init.global->objectPrototype());
|
||||
init.setStructure(structure);
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
72
src/bun.js/bindings/JSYogaConstructor.h
Normal file
72
src/bun.js/bindings/JSYogaConstructor.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/InternalFunction.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaConfigConstructor final : public JSC::InternalFunction {
|
||||
public:
|
||||
using Base = JSC::InternalFunction;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaConfigConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
|
||||
{
|
||||
JSYogaConfigConstructor* constructor = new (NotNull, JSC::allocateCell<JSYogaConfigConstructor>(vm)) JSYogaConfigConstructor(vm, structure);
|
||||
constructor->finishCreation(vm, prototype);
|
||||
return constructor;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.internalFunctionSpace();
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaConfigConstructor(JSC::VM& vm, JSC::Structure* structure);
|
||||
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype);
|
||||
};
|
||||
|
||||
class JSYogaNodeConstructor final : public JSC::InternalFunction {
|
||||
public:
|
||||
using Base = JSC::InternalFunction;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaNodeConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
|
||||
{
|
||||
JSYogaNodeConstructor* constructor = new (NotNull, JSC::allocateCell<JSYogaNodeConstructor>(vm)) JSYogaNodeConstructor(vm, structure);
|
||||
constructor->finishCreation(vm, prototype);
|
||||
return constructor;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.internalFunctionSpace();
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaNodeConstructor(JSC::VM& vm, JSC::Structure* structure);
|
||||
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype);
|
||||
};
|
||||
|
||||
// Helper functions to set up class structures
|
||||
void setupJSYogaConfigClassStructure(JSC::LazyClassStructure::Initializer&);
|
||||
void setupJSYogaNodeClassStructure(JSC::LazyClassStructure::Initializer&);
|
||||
void setupJSYogaModuleClassStructure(JSC::LazyClassStructure::Initializer&);
|
||||
|
||||
} // namespace Bun
|
||||
19
src/bun.js/bindings/JSYogaExports.cpp
Normal file
19
src/bun.js/bindings/JSYogaExports.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaConstructor.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
extern "C" {
|
||||
|
||||
JSC::EncodedJSValue Bun__JSYogaConfigConstructor(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
return JSValue::encode(globalObject->m_JSYogaConfigClassStructure.constructor(globalObject));
|
||||
}
|
||||
|
||||
JSC::EncodedJSValue Bun__JSYogaNodeConstructor(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
return JSValue::encode(globalObject->m_JSYogaNodeClassStructure.constructor(globalObject));
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
177
src/bun.js/bindings/JSYogaModule.cpp
Normal file
177
src/bun.js/bindings/JSYogaModule.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaModule.h"
|
||||
#include "JSYogaConstructor.h"
|
||||
#include "JSYogaPrototype.h"
|
||||
#include <yoga/Yoga.h>
|
||||
#include "ZigGlobalObject.h"
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
static const HashTableValue JSYogaModuleTableValues[] = {
|
||||
// Align values
|
||||
{ "ALIGN_AUTO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignAuto) } },
|
||||
{ "ALIGN_FLEX_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignFlexStart) } },
|
||||
{ "ALIGN_CENTER"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignCenter) } },
|
||||
{ "ALIGN_FLEX_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignFlexEnd) } },
|
||||
{ "ALIGN_STRETCH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignStretch) } },
|
||||
{ "ALIGN_BASELINE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignBaseline) } },
|
||||
{ "ALIGN_SPACE_BETWEEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceBetween) } },
|
||||
{ "ALIGN_SPACE_AROUND"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceAround) } },
|
||||
{ "ALIGN_SPACE_EVENLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceEvenly) } },
|
||||
|
||||
// Box sizing values
|
||||
{ "BOX_SIZING_BORDER_BOX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGBoxSizingBorderBox) } },
|
||||
{ "BOX_SIZING_CONTENT_BOX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGBoxSizingContentBox) } },
|
||||
|
||||
// Dimension values
|
||||
{ "DIMENSION_WIDTH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDimensionWidth) } },
|
||||
{ "DIMENSION_HEIGHT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDimensionHeight) } },
|
||||
|
||||
// Direction values
|
||||
{ "DIRECTION_INHERIT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionInherit) } },
|
||||
{ "DIRECTION_LTR"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionLTR) } },
|
||||
{ "DIRECTION_RTL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionRTL) } },
|
||||
|
||||
// Display values
|
||||
{ "DISPLAY_FLEX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayFlex) } },
|
||||
{ "DISPLAY_NONE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayNone) } },
|
||||
{ "DISPLAY_CONTENTS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayContents) } },
|
||||
|
||||
// Edge values
|
||||
{ "EDGE_LEFT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeLeft) } },
|
||||
{ "EDGE_TOP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeTop) } },
|
||||
{ "EDGE_RIGHT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeRight) } },
|
||||
{ "EDGE_BOTTOM"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeBottom) } },
|
||||
{ "EDGE_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeStart) } },
|
||||
{ "EDGE_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeEnd) } },
|
||||
{ "EDGE_HORIZONTAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeHorizontal) } },
|
||||
{ "EDGE_VERTICAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeVertical) } },
|
||||
{ "EDGE_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeAll) } },
|
||||
|
||||
// Errata values
|
||||
{ "ERRATA_NONE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataNone) } },
|
||||
{ "ERRATA_STRETCH_FLEX_BASIS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataStretchFlexBasis) } },
|
||||
{ "ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAbsolutePositionWithoutInsetsExcludesPadding) } },
|
||||
{ "ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAbsolutePercentAgainstInnerSize) } },
|
||||
{ "ERRATA_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAll) } },
|
||||
{ "ERRATA_CLASSIC"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataClassic) } },
|
||||
|
||||
// Experimental feature values
|
||||
{ "EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGExperimentalFeatureWebFlexBasis) } },
|
||||
|
||||
// Flex direction values
|
||||
{ "FLEX_DIRECTION_COLUMN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionColumn) } },
|
||||
{ "FLEX_DIRECTION_COLUMN_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionColumnReverse) } },
|
||||
{ "FLEX_DIRECTION_ROW"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionRow) } },
|
||||
{ "FLEX_DIRECTION_ROW_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionRowReverse) } },
|
||||
|
||||
// Gutter values
|
||||
{ "GUTTER_COLUMN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterColumn) } },
|
||||
{ "GUTTER_ROW"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterRow) } },
|
||||
{ "GUTTER_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterAll) } },
|
||||
|
||||
// Justify values
|
||||
{ "JUSTIFY_FLEX_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyFlexStart) } },
|
||||
{ "JUSTIFY_CENTER"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyCenter) } },
|
||||
{ "JUSTIFY_FLEX_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyFlexEnd) } },
|
||||
{ "JUSTIFY_SPACE_BETWEEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceBetween) } },
|
||||
{ "JUSTIFY_SPACE_AROUND"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceAround) } },
|
||||
{ "JUSTIFY_SPACE_EVENLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceEvenly) } },
|
||||
|
||||
// Log level values
|
||||
{ "LOG_LEVEL_ERROR"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelError) } },
|
||||
{ "LOG_LEVEL_WARN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelWarn) } },
|
||||
{ "LOG_LEVEL_INFO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelInfo) } },
|
||||
{ "LOG_LEVEL_DEBUG"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelDebug) } },
|
||||
{ "LOG_LEVEL_VERBOSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelVerbose) } },
|
||||
{ "LOG_LEVEL_FATAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelFatal) } },
|
||||
|
||||
// Measure mode values
|
||||
{ "MEASURE_MODE_UNDEFINED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeUndefined) } },
|
||||
{ "MEASURE_MODE_EXACTLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeExactly) } },
|
||||
{ "MEASURE_MODE_AT_MOST"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeAtMost) } },
|
||||
|
||||
// Node type values
|
||||
{ "NODE_TYPE_DEFAULT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGNodeTypeDefault) } },
|
||||
{ "NODE_TYPE_TEXT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGNodeTypeText) } },
|
||||
|
||||
// Overflow values
|
||||
{ "OVERFLOW_VISIBLE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowVisible) } },
|
||||
{ "OVERFLOW_HIDDEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowHidden) } },
|
||||
{ "OVERFLOW_SCROLL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowScroll) } },
|
||||
|
||||
// Position type values
|
||||
{ "POSITION_TYPE_STATIC"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeStatic) } },
|
||||
{ "POSITION_TYPE_RELATIVE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeRelative) } },
|
||||
{ "POSITION_TYPE_ABSOLUTE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeAbsolute) } },
|
||||
|
||||
// Unit values
|
||||
{ "UNIT_UNDEFINED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitUndefined) } },
|
||||
{ "UNIT_POINT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitPoint) } },
|
||||
{ "UNIT_PERCENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitPercent) } },
|
||||
{ "UNIT_AUTO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitAuto) } },
|
||||
{ "UNIT_MAX_CONTENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitMaxContent) } },
|
||||
{ "UNIT_FIT_CONTENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitFitContent) } },
|
||||
{ "UNIT_STRETCH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitStretch) } },
|
||||
|
||||
// Wrap values
|
||||
{ "WRAP_NO_WRAP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapNoWrap) } },
|
||||
{ "WRAP_WRAP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapWrap) } },
|
||||
{ "WRAP_WRAP_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapWrapReverse) } },
|
||||
};
|
||||
|
||||
const JSC::ClassInfo JSYogaModule::s_info = { "Yoga"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaModule) };
|
||||
|
||||
JSYogaModule* JSYogaModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSYogaModule* module = new (NotNull, allocateCell<JSYogaModule>(vm)) JSYogaModule(vm, structure);
|
||||
module->finishCreation(vm, globalObject);
|
||||
return module;
|
||||
}
|
||||
|
||||
void JSYogaModule::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Create Config constructor and prototype
|
||||
auto* configPrototype = JSYogaConfigPrototype::create(vm, globalObject,
|
||||
JSYogaConfigPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
|
||||
|
||||
auto* configConstructor = JSYogaConfigConstructor::create(vm,
|
||||
JSYogaConfigConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()),
|
||||
configPrototype);
|
||||
|
||||
// Set constructor property on prototype
|
||||
configPrototype->setConstructor(vm, configConstructor);
|
||||
|
||||
// Create Node constructor and prototype
|
||||
auto* nodePrototype = JSYogaNodePrototype::create(vm, globalObject,
|
||||
JSYogaNodePrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
|
||||
|
||||
auto* nodeConstructor = JSYogaNodeConstructor::create(vm,
|
||||
JSYogaNodeConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()),
|
||||
nodePrototype);
|
||||
|
||||
// Set constructor property on prototype
|
||||
nodePrototype->setConstructor(vm, nodeConstructor);
|
||||
|
||||
// Add constructors to module
|
||||
putDirect(vm, JSC::Identifier::fromString(vm, "Config"_s), configConstructor, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
putDirect(vm, JSC::Identifier::fromString(vm, "Node"_s), nodeConstructor, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
|
||||
// Add all Yoga constants via static hash table
|
||||
reifyStaticProperties(vm, JSYogaModule::info(), JSYogaModuleTableValues, *this);
|
||||
}
|
||||
|
||||
// Export function for Zig integration
|
||||
extern "C" JSC::EncodedJSValue Bun__createYogaModule(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
auto* structure = globalObject->JSYogaModuleStructure();
|
||||
auto* module = JSYogaModule::create(vm, globalObject, structure);
|
||||
return JSC::JSValue::encode(module);
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
36
src/bun.js/bindings/JSYogaModule.h
Normal file
36
src/bun.js/bindings/JSYogaModule.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaModule final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaModule* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*);
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaModule(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
201
src/bun.js/bindings/JSYogaNode.cpp
Normal file
201
src/bun.js/bindings/JSYogaNode.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "root.h"
|
||||
#include "JSYogaNode.h"
|
||||
#include "YogaNodeImpl.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include "JSYogaNodeOwner.h"
|
||||
#include "webcore/DOMIsoSubspaces.h"
|
||||
#include "webcore/DOMClientIsoSubspaces.h"
|
||||
#include "webcore/WebCoreJSClientData.h"
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
const JSC::ClassInfo JSYogaNode::s_info = { "Node"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaNode) };
|
||||
|
||||
JSYogaNode::JSYogaNode(JSC::VM& vm, JSC::Structure* structure, YGConfigRef config)
|
||||
: Base(vm, structure)
|
||||
, m_impl(YogaNodeImpl::create(config))
|
||||
{
|
||||
}
|
||||
|
||||
JSYogaNode::JSYogaNode(JSC::VM& vm, JSC::Structure* structure, Ref<YogaNodeImpl>&& impl)
|
||||
: Base(vm, structure)
|
||||
, m_impl(std::move(impl))
|
||||
{
|
||||
}
|
||||
|
||||
JSYogaNode::~JSYogaNode()
|
||||
{
|
||||
// The WeakHandleOwner::finalize should handle cleanup
|
||||
// Don't interfere with that mechanism
|
||||
}
|
||||
|
||||
JSYogaNode* JSYogaNode::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, YGConfigRef config, JSYogaConfig* jsConfig)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSYogaNode* node = new (NotNull, JSC::allocateCell<JSYogaNode>(vm)) JSYogaNode(vm, structure, config);
|
||||
node->finishCreation(vm, jsConfig);
|
||||
|
||||
// Initialize children array - this can throw so it must be done here
|
||||
// where exceptions can be properly propagated to callers
|
||||
JSC::JSArray* children = JSC::constructEmptyArray(globalObject, nullptr, 0);
|
||||
RETURN_IF_EXCEPTION(scope, nullptr);
|
||||
node->m_children.set(vm, node, children);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
JSYogaNode* JSYogaNode::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, Ref<YogaNodeImpl>&& impl)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSYogaNode* node = new (NotNull, JSC::allocateCell<JSYogaNode>(vm)) JSYogaNode(vm, structure, std::move(impl));
|
||||
node->finishCreation(vm);
|
||||
|
||||
// Initialize children array - this can throw so it must be done here
|
||||
// where exceptions can be properly propagated to callers
|
||||
JSC::JSArray* children = JSC::constructEmptyArray(globalObject, nullptr, 0);
|
||||
RETURN_IF_EXCEPTION(scope, nullptr);
|
||||
node->m_children.set(vm, node, children);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void JSYogaNode::finishCreation(JSC::VM& vm, JSYogaConfig* jsConfig)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Set this JS wrapper in the C++ impl
|
||||
m_impl->setJSWrapper(this);
|
||||
|
||||
// Store the JSYogaConfig if provided
|
||||
if (jsConfig) {
|
||||
m_config.set(vm, this, jsConfig);
|
||||
}
|
||||
|
||||
// Note: m_children is initialized by create() after finishCreation returns,
|
||||
// with proper exception scope handling. Do not initialize it here.
|
||||
}
|
||||
|
||||
void JSYogaNode::finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Set this JS wrapper in the C++ impl
|
||||
m_impl->setJSWrapper(this);
|
||||
|
||||
// No JSYogaConfig in this path - it's only set when explicitly provided
|
||||
|
||||
// Note: m_children is initialized by create() after finishCreation returns,
|
||||
// with proper exception scope handling. Do not initialize it here.
|
||||
}
|
||||
|
||||
JSC::Structure* JSYogaNode::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
void JSYogaNode::destroy(JSC::JSCell* cell)
|
||||
{
|
||||
auto* thisObject = static_cast<JSYogaNode*>(cell);
|
||||
|
||||
// Explicitly free the YGNode here because the ref-counting chain
|
||||
// (destroy() deref + finalize() deref -> ~YogaNodeImpl -> YGNodeFinalize)
|
||||
// may not complete during VM shutdown if WeakHandleOwner::finalize()
|
||||
// doesn't fire for all handles. This ensures the native Yoga memory
|
||||
// is always freed when the JSYogaNode cell is swept.
|
||||
auto& impl = thisObject->m_impl.get();
|
||||
YGNodeRef node = impl.yogaNode();
|
||||
if (node && impl.ownsNode()) {
|
||||
// Use YGNodeFinalize (raw delete) instead of YGNodeFree (tree-traversing)
|
||||
// because GC can sweep parent/child nodes in arbitrary order.
|
||||
YGNodeFinalize(node);
|
||||
impl.replaceYogaNode(nullptr); // Prevent double-free in ~YogaNodeImpl
|
||||
}
|
||||
|
||||
thisObject->~JSYogaNode();
|
||||
}
|
||||
|
||||
JSYogaNode* JSYogaNode::fromYGNode(YGNodeRef nodeRef)
|
||||
{
|
||||
if (!nodeRef) return nullptr;
|
||||
if (auto* impl = YogaNodeImpl::fromYGNode(nodeRef)) {
|
||||
return impl->jsWrapper();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSC::JSGlobalObject* JSYogaNode::globalObject() const
|
||||
{
|
||||
return this->structure()->globalObject();
|
||||
}
|
||||
|
||||
template<typename MyClassT, JSC::SubspaceAccess mode>
|
||||
JSC::GCClient::IsoSubspace* JSYogaNode::subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForJSYogaNode.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSYogaNode = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForJSYogaNode.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSYogaNode = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaNode::visitChildrenImpl(JSC::JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
JSYogaNode* thisObject = jsCast<JSYogaNode*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
visitor.append(thisObject->m_measureFunc);
|
||||
visitor.append(thisObject->m_dirtiedFunc);
|
||||
visitor.append(thisObject->m_baselineFunc);
|
||||
visitor.append(thisObject->m_config);
|
||||
visitor.append(thisObject->m_children);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSYogaNode);
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaNode::visitAdditionalChildren(Visitor& visitor)
|
||||
{
|
||||
visitor.append(m_measureFunc);
|
||||
visitor.append(m_dirtiedFunc);
|
||||
visitor.append(m_baselineFunc);
|
||||
visitor.append(m_config);
|
||||
visitor.append(m_children);
|
||||
|
||||
// Use the YogaNodeImpl pointer as opaque root instead of YGNodeRef
|
||||
// This avoids use-after-free when YGNode memory is freed but YogaNodeImpl still exists
|
||||
visitor.addOpaqueRoot(&m_impl.get());
|
||||
}
|
||||
|
||||
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSYogaNode);
|
||||
|
||||
template<typename Visitor>
|
||||
void JSYogaNode::visitOutputConstraints(JSC::JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
auto* thisObject = jsCast<JSYogaNode*>(cell);
|
||||
|
||||
// Lock for concurrent GC thread safety - the mutator thread may be modifying
|
||||
// WriteBarriers (m_children, m_measureFunc, etc.) concurrently via insertChild,
|
||||
// removeChild, setMeasureFunc, free(), etc. Without this lock, the GC thread
|
||||
// can read a torn/partially-written pointer from a WriteBarrier, leading to
|
||||
// a segfault in validateCell when it tries to decode a corrupted StructureID.
|
||||
WTF::Locker locker { thisObject->cellLock() };
|
||||
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitOutputConstraints(thisObject, visitor);
|
||||
|
||||
// Re-visit after mutator execution in case callbacks changed references
|
||||
// This is critical for objects whose reachability can change during runtime
|
||||
thisObject->visitAdditionalChildren(visitor);
|
||||
}
|
||||
|
||||
template void JSYogaNode::visitOutputConstraints(JSC::JSCell*, JSC::AbstractSlotVisitor&);
|
||||
template void JSYogaNode::visitOutputConstraints(JSC::JSCell*, JSC::SlotVisitor&);
|
||||
|
||||
} // namespace Bun
|
||||
66
src/bun.js/bindings/JSYogaNode.h
Normal file
66
src/bun.js/bindings/JSYogaNode.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSDestructibleObject.h>
|
||||
#include <JavaScriptCore/WriteBarrier.h>
|
||||
#include <wtf/Ref.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct YGNode* YGNodeRef;
|
||||
typedef struct YGConfig* YGConfigRef;
|
||||
typedef const struct YGNode* YGNodeConstRef;
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaConfig;
|
||||
class YogaNodeImpl;
|
||||
|
||||
class JSYogaNode final : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
static constexpr JSC::DestructionMode needsDestruction = JSC::NeedsDestruction;
|
||||
|
||||
static JSYogaNode* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, YGConfigRef config = nullptr, JSYogaConfig* jsConfig = nullptr);
|
||||
static JSYogaNode* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, Ref<YogaNodeImpl>&&);
|
||||
static void destroy(JSC::JSCell*);
|
||||
static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue);
|
||||
~JSYogaNode();
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM&);
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
template<typename Visitor> void visitAdditionalChildren(Visitor&);
|
||||
template<typename Visitor> static void visitOutputConstraints(JSC::JSCell*, Visitor&);
|
||||
|
||||
YogaNodeImpl& impl() { return m_impl.get(); }
|
||||
const YogaNodeImpl& impl() const { return m_impl.get(); }
|
||||
|
||||
// Helper to get JS wrapper from Yoga node
|
||||
static JSYogaNode* fromYGNode(YGNodeRef);
|
||||
JSC::JSGlobalObject* globalObject() const;
|
||||
|
||||
// Storage for JS callbacks
|
||||
JSC::WriteBarrier<JSC::JSObject> m_measureFunc;
|
||||
JSC::WriteBarrier<JSC::JSObject> m_dirtiedFunc;
|
||||
JSC::WriteBarrier<JSC::JSObject> m_baselineFunc;
|
||||
|
||||
// Store the JSYogaConfig that was used to create this node
|
||||
JSC::WriteBarrier<JSC::JSObject> m_config;
|
||||
|
||||
// Store children to prevent GC while still part of Yoga tree
|
||||
// This mirrors React Native's _reactSubviews NSMutableArray pattern
|
||||
JSC::WriteBarrier<JSC::JSArray> m_children;
|
||||
|
||||
private:
|
||||
JSYogaNode(JSC::VM&, JSC::Structure*, YGConfigRef config = nullptr);
|
||||
JSYogaNode(JSC::VM&, JSC::Structure*, Ref<YogaNodeImpl>&&);
|
||||
void finishCreation(JSC::VM&, JSYogaConfig* jsConfig);
|
||||
void finishCreation(JSC::VM&);
|
||||
|
||||
Ref<YogaNodeImpl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
77
src/bun.js/bindings/JSYogaNodeOwner.cpp
Normal file
77
src/bun.js/bindings/JSYogaNodeOwner.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "JSYogaNodeOwner.h"
|
||||
#include "YogaNodeImpl.h"
|
||||
#include "JSYogaNode.h"
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <wtf/NeverDestroyed.h>
|
||||
#include <wtf/Compiler.h>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
void* root(YogaNodeImpl* impl)
|
||||
{
|
||||
if (!impl)
|
||||
return nullptr;
|
||||
|
||||
YGNodeRef current = impl->yogaNode();
|
||||
YGNodeRef root = current;
|
||||
|
||||
// Traverse up to find the root node
|
||||
while (current) {
|
||||
YGNodeRef parent = YGNodeGetParent(current);
|
||||
if (!parent)
|
||||
break;
|
||||
root = parent;
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void JSYogaNodeOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
|
||||
{
|
||||
// This is where we deref the C++ YogaNodeImpl wrapper
|
||||
// The context contains our YogaNodeImpl
|
||||
auto* impl = static_cast<YogaNodeImpl*>(context);
|
||||
|
||||
// TODO: YGNodeFree during concurrent GC causes heap-use-after-free crashes
|
||||
// because YGNodeFree assumes parent/child nodes are still valid, but GC can
|
||||
// free them in arbitrary order. We need a solution that either:
|
||||
// 1. Defers YGNodeFree to run outside GC (e.g., via a cleanup queue)
|
||||
// 2. Implements reference counting at the Yoga level
|
||||
// 3. Uses a different lifecycle that mirrors React Native's manual memory management
|
||||
//
|
||||
// For now, skip YGNodeFree during GC to prevent crashes at the cost of memory leaks.
|
||||
// This matches what React Native would do if their dealloc was never called.
|
||||
|
||||
// YGNodeRef node = impl->yogaNode();
|
||||
// if (node) {
|
||||
// YGNodeFree(node);
|
||||
// }
|
||||
|
||||
// Deref the YogaNodeImpl - this will decrease its reference count
|
||||
// and potentially destroy it if no other references exist
|
||||
impl->deref();
|
||||
}
|
||||
|
||||
bool JSYogaNodeOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, JSC::AbstractSlotVisitor& visitor, ASCIILiteral* reason)
|
||||
{
|
||||
UNUSED_PARAM(handle);
|
||||
|
||||
auto* impl = static_cast<YogaNodeImpl*>(context);
|
||||
|
||||
// Standard WebKit pattern: check if reachable as opaque root
|
||||
bool reachable = visitor.containsOpaqueRoot(impl);
|
||||
if (reachable && reason)
|
||||
*reason = "YogaNode reachable from opaque root"_s;
|
||||
|
||||
return reachable;
|
||||
}
|
||||
|
||||
JSYogaNodeOwner& jsYogaNodeOwner()
|
||||
{
|
||||
static NeverDestroyed<JSYogaNodeOwner> owner;
|
||||
return owner.get();
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
23
src/bun.js/bindings/JSYogaNodeOwner.h
Normal file
23
src/bun.js/bindings/JSYogaNodeOwner.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/WeakHandleOwner.h>
|
||||
#include <JavaScriptCore/Weak.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class YogaNodeImpl;
|
||||
class JSYogaNode;
|
||||
|
||||
class JSYogaNodeOwner : public JSC::WeakHandleOwner {
|
||||
public:
|
||||
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
|
||||
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
|
||||
};
|
||||
|
||||
JSYogaNodeOwner& jsYogaNodeOwner();
|
||||
|
||||
// Helper function to get root for YogaNodeImpl
|
||||
void* root(YogaNodeImpl*);
|
||||
|
||||
} // namespace Bun
|
||||
3667
src/bun.js/bindings/JSYogaPrototype.cpp
Normal file
3667
src/bun.js/bindings/JSYogaPrototype.cpp
Normal file
File diff suppressed because it is too large
Load Diff
86
src/bun.js/bindings/JSYogaPrototype.h
Normal file
86
src/bun.js/bindings/JSYogaPrototype.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
// Base class for Yoga prototypes
|
||||
class JSYogaConfigPrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaConfigPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSYogaConfigPrototype* prototype = new (NotNull, allocateCell<JSYogaConfigPrototype>(vm)) JSYogaConfigPrototype(vm, structure);
|
||||
prototype->finishCreation(vm, globalObject);
|
||||
return prototype;
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
structure->setMayBePrototype(true);
|
||||
return structure;
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaConfigPrototype(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
|
||||
|
||||
public:
|
||||
void setConstructor(JSC::VM& vm, JSC::JSObject* constructor);
|
||||
};
|
||||
|
||||
class JSYogaNodePrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSYogaNodePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSYogaNodePrototype* prototype = new (NotNull, allocateCell<JSYogaNodePrototype>(vm)) JSYogaNodePrototype(vm, structure);
|
||||
prototype->finishCreation(vm, globalObject);
|
||||
return prototype;
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
structure->setMayBePrototype(true);
|
||||
return structure;
|
||||
}
|
||||
|
||||
private:
|
||||
JSYogaNodePrototype(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
|
||||
|
||||
public:
|
||||
void setConstructor(JSC::VM& vm, JSC::JSObject* constructor);
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
75
src/bun.js/bindings/YogaConfigImpl.cpp
Normal file
75
src/bun.js/bindings/YogaConfigImpl.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "YogaConfigImpl.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include "JSYogaConfigOwner.h"
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
Ref<YogaConfigImpl> YogaConfigImpl::create()
|
||||
{
|
||||
return adoptRef(*new YogaConfigImpl());
|
||||
}
|
||||
|
||||
YogaConfigImpl::YogaConfigImpl()
|
||||
{
|
||||
m_yogaConfig = YGConfigNew();
|
||||
|
||||
// Store this C++ wrapper in the Yoga config's context
|
||||
// Note: YGConfig doesn't have context like YGNode, so we handle this differently
|
||||
}
|
||||
|
||||
YogaConfigImpl::~YogaConfigImpl()
|
||||
{
|
||||
if (m_yogaConfig) {
|
||||
YGConfigFree(m_yogaConfig);
|
||||
m_yogaConfig = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void YogaConfigImpl::setJSWrapper(JSYogaConfig* wrapper)
|
||||
{
|
||||
// Only increment ref count if we don't already have a wrapper
|
||||
// This prevents ref count leaks if setJSWrapper is called multiple times
|
||||
if (!m_wrapper) {
|
||||
// Increment ref count for the weak handle context
|
||||
this->ref();
|
||||
}
|
||||
|
||||
// Create weak reference with our JS owner
|
||||
m_wrapper = JSC::Weak<JSYogaConfig>(wrapper, &jsYogaConfigOwner(), this);
|
||||
}
|
||||
|
||||
void YogaConfigImpl::clearJSWrapper()
|
||||
{
|
||||
m_wrapper.clear();
|
||||
}
|
||||
|
||||
void YogaConfigImpl::clearJSWrapperWithoutDeref()
|
||||
{
|
||||
// Clear weak reference without deref - used by JS destructor
|
||||
// when WeakHandleOwner::finalize will handle the deref
|
||||
m_wrapper.clear();
|
||||
}
|
||||
|
||||
JSYogaConfig* YogaConfigImpl::jsWrapper() const
|
||||
{
|
||||
return m_wrapper.get();
|
||||
}
|
||||
|
||||
YogaConfigImpl* YogaConfigImpl::fromYGConfig(YGConfigRef configRef)
|
||||
{
|
||||
// YGConfig doesn't have context storage like YGNode
|
||||
// We'd need to maintain a separate map if needed
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void YogaConfigImpl::replaceYogaConfig(YGConfigRef newConfig)
|
||||
{
|
||||
m_freed = false;
|
||||
if (m_yogaConfig) {
|
||||
YGConfigFree(m_yogaConfig);
|
||||
}
|
||||
m_yogaConfig = newConfig;
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
44
src/bun.js/bindings/YogaConfigImpl.h
Normal file
44
src/bun.js/bindings/YogaConfigImpl.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <wtf/RefCounted.h>
|
||||
#include <JavaScriptCore/Weak.h>
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaConfig;
|
||||
|
||||
class YogaConfigImpl : public RefCounted<YogaConfigImpl> {
|
||||
public:
|
||||
static Ref<YogaConfigImpl> create();
|
||||
~YogaConfigImpl();
|
||||
|
||||
YGConfigRef yogaConfig() const { return m_freed ? nullptr : m_yogaConfig; }
|
||||
|
||||
// JS wrapper management
|
||||
void setJSWrapper(JSYogaConfig*);
|
||||
void clearJSWrapper();
|
||||
void clearJSWrapperWithoutDeref(); // Clear weak ref without deref (for JS destructor)
|
||||
JSYogaConfig* jsWrapper() const;
|
||||
|
||||
// Helper to get YogaConfigImpl from YGConfigRef
|
||||
static YogaConfigImpl* fromYGConfig(YGConfigRef);
|
||||
|
||||
// Replace the internal YGConfigRef (used for advanced cases)
|
||||
void replaceYogaConfig(YGConfigRef newConfig);
|
||||
|
||||
// Mark as freed (for JS free() method validation)
|
||||
void markAsFreed() { m_freed = true; }
|
||||
bool isFreed() const { return m_freed; }
|
||||
|
||||
private:
|
||||
explicit YogaConfigImpl();
|
||||
|
||||
YGConfigRef m_yogaConfig;
|
||||
JSC::Weak<JSYogaConfig> m_wrapper;
|
||||
bool m_freed { false };
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
111
src/bun.js/bindings/YogaNodeImpl.cpp
Normal file
111
src/bun.js/bindings/YogaNodeImpl.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "YogaNodeImpl.h"
|
||||
#include "JSYogaNode.h"
|
||||
#include "JSYogaConfig.h"
|
||||
#include "JSYogaNodeOwner.h"
|
||||
#include <yoga/Yoga.h>
|
||||
#include <wtf/HashSet.h>
|
||||
#include <wtf/Lock.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
Ref<YogaNodeImpl> YogaNodeImpl::create(YGConfigRef config)
|
||||
{
|
||||
return adoptRef(*new YogaNodeImpl(config));
|
||||
}
|
||||
|
||||
YogaNodeImpl::YogaNodeImpl(YGConfigRef config)
|
||||
{
|
||||
if (config) {
|
||||
m_yogaNode = YGNodeNewWithConfig(config);
|
||||
} else {
|
||||
m_yogaNode = YGNodeNew();
|
||||
}
|
||||
|
||||
// Store this C++ wrapper in the Yoga node's context
|
||||
YGNodeSetContext(m_yogaNode, this);
|
||||
}
|
||||
|
||||
YogaNodeImpl::~YogaNodeImpl()
|
||||
{
|
||||
// Free the underlying Yoga node if it hasn't been freed already.
|
||||
// When the user calls .free() explicitly, replaceYogaNode(nullptr) sets
|
||||
// m_yogaNode to null first, so this guard prevents double-free.
|
||||
if (m_yogaNode && m_ownsNode) {
|
||||
// Use YGNodeFinalize instead of YGNodeFree: it frees the node's
|
||||
// memory without disconnecting it from its owner or children.
|
||||
// This is safe during GC, where nodes in the same tree may be
|
||||
// swept in arbitrary order and parent/child pointers may already
|
||||
// be dangling.
|
||||
YGNodeFinalize(m_yogaNode);
|
||||
}
|
||||
m_yogaNode = nullptr;
|
||||
}
|
||||
|
||||
void YogaNodeImpl::setJSWrapper(JSYogaNode* wrapper)
|
||||
{
|
||||
// Only increment ref count if we don't already have a wrapper
|
||||
// This prevents ref count leaks if setJSWrapper is called multiple times
|
||||
if (!m_wrapper) {
|
||||
// Increment ref count for the weak handle context
|
||||
this->ref();
|
||||
}
|
||||
|
||||
// Create weak reference with our JS owner
|
||||
m_wrapper = JSC::Weak<JSYogaNode>(wrapper, &jsYogaNodeOwner(), this);
|
||||
}
|
||||
|
||||
void YogaNodeImpl::clearJSWrapper()
|
||||
{
|
||||
m_wrapper.clear();
|
||||
}
|
||||
|
||||
void YogaNodeImpl::clearJSWrapperWithoutDeref()
|
||||
{
|
||||
// Clear weak reference without deref - used by JS destructor
|
||||
// when WeakHandleOwner::finalize will handle the deref
|
||||
m_wrapper.clear();
|
||||
}
|
||||
|
||||
JSYogaNode* YogaNodeImpl::jsWrapper() const
|
||||
{
|
||||
return m_wrapper.get();
|
||||
}
|
||||
|
||||
JSYogaConfig* YogaNodeImpl::jsConfig() const
|
||||
{
|
||||
// Access config through JS wrapper's WriteBarrier - this is GC-safe
|
||||
if (auto* jsWrapper = m_wrapper.get()) {
|
||||
return jsCast<JSYogaConfig*>(jsWrapper->m_config.get());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
YogaNodeImpl* YogaNodeImpl::fromYGNode(YGNodeRef nodeRef)
|
||||
{
|
||||
if (!nodeRef) return nullptr;
|
||||
return static_cast<YogaNodeImpl*>(YGNodeGetContext(nodeRef));
|
||||
}
|
||||
|
||||
void YogaNodeImpl::replaceYogaNode(YGNodeRef newNode)
|
||||
{
|
||||
if (newNode) {
|
||||
// Free the old node if we are replacing it with a different one.
|
||||
// This prevents leaks when, e.g., the clone path creates a throwaway
|
||||
// YGNode via create(nullptr) and immediately replaces it.
|
||||
if (m_yogaNode && m_yogaNode != newNode && m_ownsNode) {
|
||||
YGNodeFinalize(m_yogaNode);
|
||||
}
|
||||
|
||||
// Update the context pointer to point to this impl.
|
||||
// YGNodeClone performs a deep clone (new YGNode objects throughout),
|
||||
// so there is no sharing of nodes and no previous owner to notify.
|
||||
YGNodeSetContext(newNode, this);
|
||||
}
|
||||
|
||||
// When newNode is null (called from .free() after YGNodeFree), the old
|
||||
// m_yogaNode was already freed by the caller -- just clear the pointer.
|
||||
m_yogaNode = newNode;
|
||||
m_ownsNode = (newNode != nullptr);
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
45
src/bun.js/bindings/YogaNodeImpl.h
Normal file
45
src/bun.js/bindings/YogaNodeImpl.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <wtf/RefCounted.h>
|
||||
#include <JavaScriptCore/Weak.h>
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
#include <yoga/Yoga.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSYogaNode;
|
||||
class JSYogaConfig;
|
||||
|
||||
class YogaNodeImpl : public RefCounted<YogaNodeImpl> {
|
||||
public:
|
||||
static Ref<YogaNodeImpl> create(YGConfigRef config = nullptr);
|
||||
~YogaNodeImpl();
|
||||
|
||||
YGNodeRef yogaNode() const { return m_yogaNode; }
|
||||
bool ownsNode() const { return m_ownsNode; }
|
||||
|
||||
// JS wrapper management
|
||||
void setJSWrapper(JSYogaNode*);
|
||||
void clearJSWrapper();
|
||||
void clearJSWrapperWithoutDeref(); // Clear weak ref without deref (for JS destructor)
|
||||
JSYogaNode* jsWrapper() const;
|
||||
|
||||
// Config access through JS wrapper's WriteBarrier
|
||||
JSYogaConfig* jsConfig() const;
|
||||
|
||||
// Helper to get YogaNodeImpl from YGNodeRef
|
||||
static YogaNodeImpl* fromYGNode(YGNodeRef);
|
||||
|
||||
// Replace the internal YGNodeRef (used for cloning)
|
||||
void replaceYogaNode(YGNodeRef newNode);
|
||||
|
||||
private:
|
||||
explicit YogaNodeImpl(YGConfigRef config);
|
||||
|
||||
YGNodeRef m_yogaNode;
|
||||
bool m_ownsNode { true };
|
||||
JSC::Weak<JSYogaNode> m_wrapper;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
@@ -194,6 +194,7 @@
|
||||
#include "node/NodeTimers.h"
|
||||
#include "JSConnectionsList.h"
|
||||
#include "JSHTTPParser.h"
|
||||
#include "JSYogaConstructor.h"
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include "JSBunRequest.h"
|
||||
@@ -1795,6 +1796,19 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
setupHTTPParserClassStructure(init);
|
||||
});
|
||||
|
||||
m_JSYogaConfigClassStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
setupJSYogaConfigClassStructure(init);
|
||||
});
|
||||
m_JSYogaNodeClassStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
setupJSYogaNodeClassStructure(init);
|
||||
});
|
||||
m_JSYogaModuleStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
setupJSYogaModuleClassStructure(init);
|
||||
});
|
||||
|
||||
m_JSNodePerformanceHooksHistogramClassStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
Bun::setupJSNodePerformanceHooksHistogramClassStructure(init);
|
||||
|
||||
@@ -316,6 +316,8 @@ public:
|
||||
|
||||
Structure* JSSQLStatementStructure() const { return m_JSSQLStatementStructure.getInitializedOnMainThread(this); }
|
||||
|
||||
JSC::Structure* JSYogaModuleStructure() const { return m_JSYogaModuleStructure.getInitializedOnMainThread(this); }
|
||||
|
||||
v8::shim::GlobalInternals* V8GlobalInternals() const { return m_V8GlobalInternals.getInitializedOnMainThread(this); }
|
||||
|
||||
Bun::BakeAdditionsToGlobalObject& bakeAdditions() { return m_bakeAdditions; }
|
||||
@@ -640,7 +642,11 @@ public:
|
||||
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMDontContextify) \
|
||||
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMUseMainContextDefaultLoader) \
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcSerializeFunction) \
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction)
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction) \
|
||||
\
|
||||
V(public, LazyClassStructure, m_JSYogaConfigClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSYogaNodeClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSYogaModuleStructure)
|
||||
|
||||
#define DECLARE_GLOBALOBJECT_GC_MEMBER(visibility, T, name) \
|
||||
visibility: \
|
||||
|
||||
@@ -954,5 +954,7 @@ public:
|
||||
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSConnectionsList;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSHTTPParser;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSYogaConfig;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSYogaNode;
|
||||
};
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -957,6 +957,8 @@ public:
|
||||
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSConnectionsList;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSHTTPParser;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSYogaConfig;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSYogaNode;
|
||||
};
|
||||
} // namespace WebCore
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
#include <JavaScriptCore/ArrayBuffer.h>
|
||||
#include <JavaScriptCore/JSArrayBufferView.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/JSArrayInlines.h>
|
||||
#include <JavaScriptCore/ButterflyInlines.h>
|
||||
#include <JavaScriptCore/ObjectInitializationScope.h>
|
||||
#include <JavaScriptCore/JSDataView.h>
|
||||
#include <JavaScriptCore/JSMapInlines.h>
|
||||
#include <JavaScriptCore/JSMapIterator.h>
|
||||
@@ -5574,6 +5577,13 @@ SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleInMemoryProp
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements)
|
||||
: m_simpleArrayElements(WTF::move(elements))
|
||||
, m_fastPath(FastPath::SimpleArray)
|
||||
{
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
|
||||
: m_fastPathString(fastPathString)
|
||||
, m_fastPath(FastPath::String)
|
||||
@@ -5581,6 +5591,14 @@ SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath)
|
||||
: m_arrayButterflyData(WTF::move(butterflyData))
|
||||
, m_arrayLength(length)
|
||||
, m_fastPath(fastPath)
|
||||
{
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
size_t SerializedScriptValue::computeMemoryCost() const
|
||||
{
|
||||
size_t cost = m_data.size();
|
||||
@@ -5652,6 +5670,19 @@ size_t SerializedScriptValue::computeMemoryCost() const
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case FastPath::SimpleArray:
|
||||
cost += m_simpleArrayElements.byteSize();
|
||||
for (const auto& elem : m_simpleArrayElements) {
|
||||
std::visit(WTF::makeVisitor(
|
||||
[&](JSC::JSValue) { /* already included in byteSize() */ },
|
||||
[&](const String& s) { cost += s.sizeInBytes(); }),
|
||||
elem);
|
||||
}
|
||||
break;
|
||||
case FastPath::Int32Array:
|
||||
case FastPath::DoubleArray:
|
||||
cost += m_arrayButterflyData.size();
|
||||
break;
|
||||
case FastPath::None:
|
||||
break;
|
||||
@@ -5843,7 +5874,9 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
if (canUseFastPath) {
|
||||
bool canUseStringFastPath = false;
|
||||
bool canUseObjectFastPath = false;
|
||||
bool canUseArrayFastPath = false;
|
||||
JSObject* object = nullptr;
|
||||
JSArray* array = nullptr;
|
||||
Structure* structure = nullptr;
|
||||
if (value.isCell()) {
|
||||
auto* cell = value.asCell();
|
||||
@@ -5853,7 +5886,10 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
object = cell->getObject();
|
||||
structure = object->structure();
|
||||
|
||||
if (isObjectFastPathCandidate(structure)) {
|
||||
if (auto* jsArray = jsDynamicCast<JSArray*>(object)) {
|
||||
canUseArrayFastPath = true;
|
||||
array = jsArray;
|
||||
} else if (isObjectFastPathCandidate(structure)) {
|
||||
canUseObjectFastPath = true;
|
||||
}
|
||||
}
|
||||
@@ -5866,6 +5902,84 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
return SerializedScriptValue::createStringFastPath(stringValue);
|
||||
}
|
||||
|
||||
if (canUseArrayFastPath) {
|
||||
ASSERT(array != nullptr);
|
||||
// Arrays with named properties (e.g. arr.foo = "bar") cannot use fast path
|
||||
// as we only copy indexed elements. maxOffset == invalidOffset means no named properties.
|
||||
if (structure->maxOffset() != invalidOffset)
|
||||
canUseArrayFastPath = false;
|
||||
}
|
||||
|
||||
if (canUseArrayFastPath) {
|
||||
ASSERT(array != nullptr);
|
||||
unsigned length = array->length();
|
||||
auto arrayType = array->indexingType();
|
||||
|
||||
// Tier 1/2: Int32 / Double butterfly memcpy fast path
|
||||
if ((arrayType == ArrayWithInt32 || arrayType == ArrayWithDouble)
|
||||
&& length <= array->butterfly()->vectorLength()
|
||||
&& !array->structure()->holesMustForwardToPrototype(array)) {
|
||||
|
||||
if (arrayType == ArrayWithInt32) {
|
||||
auto* data = array->butterfly()->contiguous().data();
|
||||
if (!containsHole(data, length)) {
|
||||
size_t byteSize = sizeof(JSValue) * length;
|
||||
Vector<uint8_t> buffer(byteSize, 0);
|
||||
memcpy(buffer.mutableSpan().data(), data, byteSize);
|
||||
return SerializedScriptValue::createInt32ArrayFastPath(WTF::move(buffer), length);
|
||||
}
|
||||
} else {
|
||||
auto* data = array->butterfly()->contiguousDouble().data();
|
||||
if (!containsHole(data, length)) {
|
||||
size_t byteSize = sizeof(double) * length;
|
||||
Vector<uint8_t> buffer(byteSize, 0);
|
||||
memcpy(buffer.mutableSpan().data(), data, byteSize);
|
||||
return SerializedScriptValue::createDoubleArrayFastPath(WTF::move(buffer), length);
|
||||
}
|
||||
}
|
||||
// Holes present → fall through to normal path
|
||||
}
|
||||
|
||||
// Tier 3: Contiguous array with butterfly direct access
|
||||
if (arrayType == ArrayWithContiguous
|
||||
&& length <= array->butterfly()->vectorLength()
|
||||
&& !array->structure()->holesMustForwardToPrototype(array)) {
|
||||
|
||||
auto* data = array->butterfly()->contiguous().data();
|
||||
WTF::Vector<SimpleCloneableValue> elements;
|
||||
elements.reserveInitialCapacity(length);
|
||||
bool ok = true;
|
||||
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue elem = data[i].get();
|
||||
if (!elem) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (elem.isCell()) {
|
||||
if (!elem.isString()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
auto* str = asString(elem);
|
||||
String strValue = str->value(&lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
|
||||
elements.append(Bun::toCrossThreadShareable(strValue));
|
||||
} else {
|
||||
elements.append(elem);
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return SerializedScriptValue::createArrayFastPath(
|
||||
WTF::FixedVector<SimpleCloneableValue>(WTF::move(elements)));
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayStorage / Undecided / holes forwarding → fall through to normal serialization path
|
||||
}
|
||||
|
||||
if (canUseObjectFastPath) {
|
||||
ASSERT(object != nullptr);
|
||||
|
||||
@@ -6142,6 +6256,21 @@ Ref<SerializedScriptValue> SerializedScriptValue::createObjectFastPath(WTF::Fixe
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(object)));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(elements)));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createInt32ArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::Int32Array));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createDoubleArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::DoubleArray));
|
||||
}
|
||||
|
||||
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception)
|
||||
{
|
||||
JSGlobalObject* lexicalGlobalObject = toJS(originContext);
|
||||
@@ -6288,6 +6417,78 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject,
|
||||
|
||||
return object;
|
||||
}
|
||||
case FastPath::SimpleArray: {
|
||||
unsigned length = m_simpleArrayElements.size();
|
||||
|
||||
// Pre-convert all elements to JSValues (including creating JSStrings)
|
||||
// before entering ObjectInitializationScope, since jsString() allocates
|
||||
// GC cells which is not allowed inside the initialization scope.
|
||||
MarkedArgumentBuffer values;
|
||||
values.ensureCapacity(length);
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue elemValue = std::visit(
|
||||
WTF::makeVisitor(
|
||||
[](JSValue v) -> JSValue { return v; },
|
||||
[&](const String& s) -> JSValue { return jsString(vm, s); }),
|
||||
m_simpleArrayElements[i]);
|
||||
values.append(elemValue);
|
||||
}
|
||||
|
||||
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous);
|
||||
ObjectInitializationScope initScope(vm);
|
||||
JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(initScope, resultStructure, length);
|
||||
|
||||
if (!resultArray) [[unlikely]] {
|
||||
if (didFail)
|
||||
*didFail = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < length; i++)
|
||||
resultArray->initializeIndex(initScope, i, values.at(i));
|
||||
|
||||
if (didFail)
|
||||
*didFail = false;
|
||||
return resultArray;
|
||||
}
|
||||
case FastPath::Int32Array:
|
||||
case FastPath::DoubleArray: {
|
||||
IndexingType arrayType = (m_fastPath == FastPath::Int32Array) ? ArrayWithInt32 : ArrayWithDouble;
|
||||
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(arrayType);
|
||||
|
||||
if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]]
|
||||
break; // isHavingABadTime → fall through to normal deserialization
|
||||
|
||||
unsigned outOfLineStorage = resultStructure->outOfLineCapacity();
|
||||
unsigned vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, m_arrayLength);
|
||||
void* memory = vm.auxiliarySpace().allocate(
|
||||
vm,
|
||||
Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)),
|
||||
nullptr, AllocationFailureMode::ReturnNull);
|
||||
|
||||
if (!memory) [[unlikely]] {
|
||||
if (didFail)
|
||||
*didFail = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
Butterfly* butterfly = Butterfly::fromBase(memory, 0, outOfLineStorage);
|
||||
butterfly->setVectorLength(vectorLength);
|
||||
butterfly->setPublicLength(m_arrayLength);
|
||||
|
||||
if (m_fastPath == FastPath::DoubleArray)
|
||||
memcpy(butterfly->contiguousDouble().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
|
||||
else
|
||||
memcpy(butterfly->contiguous().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
|
||||
|
||||
// Clear unused tail slots with hole values
|
||||
Butterfly::clearRange(arrayType, butterfly, m_arrayLength, vectorLength);
|
||||
|
||||
JSArray* resultArray = JSArray::createWithButterfly(vm, nullptr, resultStructure, butterfly);
|
||||
if (didFail)
|
||||
*didFail = false;
|
||||
return resultArray;
|
||||
}
|
||||
case FastPath::None: {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -60,15 +60,12 @@ class MemoryHandle;
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
// Shared value type for fast path cloning: primitives (JSValue) or strings.
|
||||
using SimpleCloneableValue = std::variant<JSC::JSValue, WTF::String>;
|
||||
|
||||
class SimpleInMemoryPropertyTableEntry {
|
||||
public:
|
||||
// Only:
|
||||
// - String
|
||||
// - Number
|
||||
// - Boolean
|
||||
// - Null
|
||||
// - Undefined
|
||||
using Value = std::variant<JSC::JSValue, WTF::String>;
|
||||
using Value = SimpleCloneableValue;
|
||||
|
||||
WTF::String propertyName;
|
||||
Value value;
|
||||
@@ -78,6 +75,9 @@ enum class FastPath : uint8_t {
|
||||
None,
|
||||
String,
|
||||
SimpleObject,
|
||||
SimpleArray,
|
||||
Int32Array,
|
||||
DoubleArray,
|
||||
};
|
||||
|
||||
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
|
||||
@@ -129,6 +129,13 @@ public:
|
||||
// Fast path for postMessage with simple objects
|
||||
static Ref<SerializedScriptValue> createObjectFastPath(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
|
||||
|
||||
// Fast path for postMessage with dense arrays of primitives/strings
|
||||
static Ref<SerializedScriptValue> createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements);
|
||||
|
||||
// Fast path for postMessage with dense Int32/Double arrays (butterfly memcpy)
|
||||
static Ref<SerializedScriptValue> createInt32ArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
|
||||
static Ref<SerializedScriptValue> createDoubleArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
|
||||
|
||||
static Ref<SerializedScriptValue> nullValue();
|
||||
|
||||
WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr);
|
||||
@@ -231,6 +238,9 @@ private:
|
||||
// Constructor for string fast path
|
||||
explicit SerializedScriptValue(const String& fastPathString);
|
||||
explicit SerializedScriptValue(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
|
||||
explicit SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements);
|
||||
// Constructor for Int32Array/DoubleArray butterfly memcpy fast path
|
||||
SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath);
|
||||
|
||||
size_t computeMemoryCost() const;
|
||||
|
||||
@@ -260,6 +270,13 @@ private:
|
||||
size_t m_memoryCost { 0 };
|
||||
|
||||
FixedVector<SimpleInMemoryPropertyTableEntry> m_simpleInMemoryPropertyTable {};
|
||||
// m_simpleArrayElements and m_arrayButterflyData/m_arrayLength are used exclusively:
|
||||
// SimpleArray uses m_simpleArrayElements; Int32Array/DoubleArray use m_arrayButterflyData + m_arrayLength.
|
||||
FixedVector<SimpleCloneableValue> m_simpleArrayElements {};
|
||||
|
||||
// Int32Array / DoubleArray fast path: raw butterfly data
|
||||
Vector<uint8_t> m_arrayButterflyData {};
|
||||
uint32_t m_arrayLength { 0 };
|
||||
};
|
||||
|
||||
template<class Encoder>
|
||||
|
||||
@@ -351,11 +351,13 @@ pub fn autoTick(this: *EventLoop) void {
|
||||
const ctx = this.virtual_machine;
|
||||
|
||||
this.tickImmediateTasks(ctx);
|
||||
if (comptime Environment.isPosix) {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (this.immediate_tasks.items.len > 0) {
|
||||
this.wakeup();
|
||||
}
|
||||
}
|
||||
// On POSIX, pending immediates are handled via an immediate timeout in
|
||||
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
// Some tasks need to keep the event loop alive for one more tick.
|
||||
@@ -438,11 +440,13 @@ pub fn autoTickActive(this: *EventLoop) void {
|
||||
var ctx = this.virtual_machine;
|
||||
|
||||
this.tickImmediateTasks(ctx);
|
||||
if (comptime Environment.isPosix) {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (this.immediate_tasks.items.len > 0) {
|
||||
this.wakeup();
|
||||
}
|
||||
}
|
||||
// On POSIX, pending immediates are handled via an immediate timeout in
|
||||
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
const pending_unref = ctx.pending_unref_counter;
|
||||
|
||||
@@ -16,6 +16,10 @@ pub const PosixLoop = extern struct {
|
||||
/// Number of polls owned by Bun
|
||||
active: u32 = 0,
|
||||
|
||||
/// Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
|
||||
/// If non-zero, the event loop will return immediately so we can skip the GC safepoint.
|
||||
pending_wakeups: u32 = 0,
|
||||
|
||||
/// The list of ready polls
|
||||
ready_polls: [1024]EventType align(16),
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const Loop = struct {
|
||||
|
||||
{
|
||||
var epoll = std.mem.zeroes(std.os.linux.epoll_event);
|
||||
epoll.events = std.os.linux.EPOLL.IN | std.os.linux.EPOLL.ERR | std.os.linux.EPOLL.HUP;
|
||||
epoll.events = std.os.linux.EPOLL.IN | std.os.linux.EPOLL.ET | std.os.linux.EPOLL.ERR | std.os.linux.EPOLL.HUP;
|
||||
epoll.data.ptr = @intFromPtr(&loop);
|
||||
const rc = std.os.linux.epoll_ctl(loop.epoll_fd.cast(), std.os.linux.EPOLL.CTL_ADD, loop.waker.getFd().cast(), &epoll);
|
||||
|
||||
@@ -165,9 +165,8 @@ pub const Loop = struct {
|
||||
const pollable: Pollable = Pollable.from(event.data.u64);
|
||||
if (pollable.tag() == .empty) {
|
||||
if (event.data.ptr == @intFromPtr(&loop)) {
|
||||
// this is the event poll, lets read it
|
||||
var bytes: [8]u8 = undefined;
|
||||
_ = bun.sys.read(loop.fd(), &bytes);
|
||||
// Edge-triggered: no need to read the eventfd counter
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ = Poll.onUpdateEpoll(pollable.poll(), pollable.tag(), event);
|
||||
|
||||
@@ -664,28 +664,6 @@ export function toJSON(this: BufferExt) {
|
||||
return { type, data };
|
||||
}
|
||||
|
||||
export function slice(this: BufferExt, start, end) {
|
||||
var { buffer, byteOffset, byteLength } = this;
|
||||
|
||||
function adjustOffset(offset, length) {
|
||||
// Use Math.trunc() to convert offset to an integer value that can be larger
|
||||
// than an Int32. Hence, don't use offset | 0 or similar techniques.
|
||||
offset = Math.trunc(offset);
|
||||
if (offset === 0 || offset !== offset) {
|
||||
return 0;
|
||||
} else if (offset < 0) {
|
||||
offset += length;
|
||||
return offset > 0 ? offset : 0;
|
||||
} else {
|
||||
return offset < length ? offset : length;
|
||||
}
|
||||
}
|
||||
|
||||
var start_ = adjustOffset(start, byteLength);
|
||||
var end_ = end !== undefined ? adjustOffset(end, byteLength) : byteLength;
|
||||
return new $Buffer(buffer, byteOffset + start_, end_ > start_ ? end_ - start_ : 0);
|
||||
}
|
||||
|
||||
$getter;
|
||||
export function parent(this: BufferExt) {
|
||||
return $isObject(this) && this instanceof $Buffer ? this.buffer : undefined;
|
||||
|
||||
@@ -19,7 +19,6 @@ expectAssignable<Bun.Build.CompileTarget>("bun-windows-x64-modern");
|
||||
Bun.build({
|
||||
entrypoints: ["hey"],
|
||||
splitting: false,
|
||||
// @ts-expect-error Currently not supported
|
||||
compile: {},
|
||||
});
|
||||
|
||||
|
||||
94
test/js/bun/net/kqueue-filter-coalesce-fixture.ts
generated
Normal file
94
test/js/bun/net/kqueue-filter-coalesce-fixture.ts
generated
Normal file
@@ -0,0 +1,94 @@
|
||||
// Regression test for kqueue filter comparison bug (macOS).
|
||||
//
|
||||
// On kqueue, EVFILT_READ (-1) and EVFILT_WRITE (-2) are negative integers. The old
|
||||
// code used bitwise AND to identify filters:
|
||||
//
|
||||
// events |= (filter & EVFILT_READ) ? READABLE : 0
|
||||
// events |= (filter & EVFILT_WRITE) ? WRITABLE : 0
|
||||
//
|
||||
// Since all negative numbers AND'd with -1 or -2 produce truthy values, EVERY kqueue
|
||||
// event was misidentified as BOTH readable AND writable. This caused the drain handler
|
||||
// to fire spuriously on every readable event and vice versa.
|
||||
//
|
||||
// The fix uses equality comparison (filter == EVFILT_READ), plus coalescing duplicate
|
||||
// kevents for the same fd (kqueue returns separate events per filter) into a single
|
||||
// dispatch with combined flags — matching epoll's single-entry-per-fd behavior.
|
||||
//
|
||||
// This test creates unix socket connections with small buffers to force partial writes
|
||||
// (which registers EVFILT_WRITE). The client sends pings on each data callback, causing
|
||||
// EVFILT_READ events on the server. With the bug, each EVFILT_READ also triggers drain,
|
||||
// giving a drain/data ratio of ~2.0. With the fix, the ratio is ~1.0.
|
||||
//
|
||||
// Example output:
|
||||
// system bun (bug): data: 38970 drain: 77940 ratio: 2.0
|
||||
// fixed bun: data: 52965 drain: 52965 ratio: 1.0
|
||||
|
||||
import { setSocketOptions } from "bun:internal-for-testing";
|
||||
|
||||
const CHUNK = Buffer.alloc(64 * 1024, "x");
|
||||
const PING = Buffer.from("p");
|
||||
const sockPath = `kqueue-bench-${process.pid}.sock`;
|
||||
|
||||
let drainCalls = 0;
|
||||
let dataCalls = 0;
|
||||
|
||||
const server = Bun.listen({
|
||||
unix: sockPath,
|
||||
socket: {
|
||||
open(socket) {
|
||||
setSocketOptions(socket, 1, 512);
|
||||
setSocketOptions(socket, 2, 512);
|
||||
socket.write(CHUNK);
|
||||
},
|
||||
data() {
|
||||
dataCalls++;
|
||||
},
|
||||
drain(socket) {
|
||||
drainCalls++;
|
||||
socket.write(CHUNK);
|
||||
},
|
||||
close() {},
|
||||
error() {},
|
||||
},
|
||||
});
|
||||
|
||||
const clients = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
clients.push(
|
||||
await Bun.connect({
|
||||
unix: sockPath,
|
||||
socket: {
|
||||
open(socket) {
|
||||
setSocketOptions(socket, 1, 512);
|
||||
setSocketOptions(socket, 2, 512);
|
||||
},
|
||||
data(socket) {
|
||||
socket.write(PING);
|
||||
},
|
||||
drain() {},
|
||||
close() {},
|
||||
error() {},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Bun.sleep(50);
|
||||
drainCalls = 0;
|
||||
dataCalls = 0;
|
||||
|
||||
await Bun.sleep(100);
|
||||
|
||||
const ratio = dataCalls > 0 ? drainCalls / dataCalls : 0;
|
||||
console.log(`data: ${dataCalls} drain: ${drainCalls} ratio: ${ratio.toFixed(1)}`);
|
||||
|
||||
for (const c of clients) c.end();
|
||||
server.stop(true);
|
||||
try {
|
||||
require("fs").unlinkSync(sockPath);
|
||||
} catch {}
|
||||
if (dataCalls === 0 || drainCalls === 0) {
|
||||
console.error("test invalid: no data or drain callbacks fired");
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(ratio < 1.5 ? 0 : 1);
|
||||
@@ -339,6 +339,10 @@ describe.concurrent("socket", () => {
|
||||
expect([fileURLToPath(new URL("./socket-huge-fixture.js", import.meta.url))]).toRun();
|
||||
}, 60_000);
|
||||
|
||||
it.skipIf(isWindows)("kqueue should not dispatch spurious drain events on readable", async () => {
|
||||
expect([fileURLToPath(new URL("./kqueue-filter-coalesce-fixture.ts", import.meta.url))]).toRun();
|
||||
});
|
||||
|
||||
it("it should not crash when getting a ReferenceError on client socket open", async () => {
|
||||
using server = Bun.serve({
|
||||
port: 0,
|
||||
|
||||
@@ -68,6 +68,6 @@ describe("static initializers", () => {
|
||||
expect(
|
||||
bunInitializers.length,
|
||||
`Do not add static initializers to Bun. Static initializers are called when Bun starts up, regardless of whether you use the variables or not. This makes Bun slower.`,
|
||||
).toBe(process.arch === "arm64" ? 2 : 3);
|
||||
).toBe(process.arch === "arm64" ? 1 : 2);
|
||||
});
|
||||
});
|
||||
|
||||
1911
test/js/bun/yoga/YGAbsolutePositionTest.test.ts
Normal file
1911
test/js/bun/yoga/YGAbsolutePositionTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
125
test/js/bun/yoga/YGAlignBaselineTest.test.ts
Normal file
125
test/js/bun/yoga/YGAlignBaselineTest.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("align_baseline_parent_using_child_in_column_as_reference", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
root.setWidth(1000);
|
||||
root.setHeight(1000);
|
||||
root.setAlignItems(Yoga.ALIGN_BASELINE);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child0.setWidth(500);
|
||||
root_child0.setHeight(600);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child1.setWidth(500);
|
||||
root_child1.setHeight(800);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child1_child0 = Yoga.Node.create(config);
|
||||
root_child1_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child1_child0.setWidth(500);
|
||||
root_child1_child0.setHeight(300);
|
||||
root_child1.insertChild(root_child1_child0, 0);
|
||||
|
||||
const root_child1_child1 = Yoga.Node.create(config);
|
||||
root_child1_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child1_child1.setWidth(500);
|
||||
root_child1_child1.setHeight(400);
|
||||
root_child1_child1.setIsReferenceBaseline(true);
|
||||
root_child1.insertChild(root_child1_child1, 1);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(500);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
|
||||
expect(root_child1_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child1_child0.getComputedTop()).toBe(0);
|
||||
|
||||
expect(root_child1_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1_child1.getComputedTop()).toBe(300);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
|
||||
test("align_baseline_parent_using_child_in_row_as_reference", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
root.setWidth(1000);
|
||||
root.setHeight(1000);
|
||||
root.setAlignItems(Yoga.ALIGN_BASELINE);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child0.setWidth(500);
|
||||
root_child0.setHeight(600);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
root_child1.setWidth(500);
|
||||
root_child1.setHeight(800);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child1_child0 = Yoga.Node.create(config);
|
||||
root_child1_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child1_child0.setWidth(500);
|
||||
root_child1_child0.setHeight(500);
|
||||
root_child1.insertChild(root_child1_child0, 0);
|
||||
|
||||
const root_child1_child1 = Yoga.Node.create(config);
|
||||
root_child1_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
root_child1_child1.setWidth(500);
|
||||
root_child1_child1.setHeight(400);
|
||||
root_child1_child1.setIsReferenceBaseline(true);
|
||||
root_child1.insertChild(root_child1_child1, 1);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(500);
|
||||
expect(root_child1.getComputedTop()).toBe(200);
|
||||
|
||||
expect(root_child1_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child1_child0.getComputedTop()).toBe(0);
|
||||
|
||||
expect(root_child1_child1.getComputedLeft()).toBe(500);
|
||||
expect(root_child1_child1.getComputedTop()).toBe(0);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
5910
test/js/bun/yoga/YGAlignContentTest.test.ts
Normal file
5910
test/js/bun/yoga/YGAlignContentTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
2610
test/js/bun/yoga/YGAlignItemsTest.test.ts
Normal file
2610
test/js/bun/yoga/YGAlignItemsTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
437
test/js/bun/yoga/YGAlignSelfTest.test.ts
Normal file
437
test/js/bun/yoga/YGAlignSelfTest.test.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<1badc9ed5a0cb8d9a4a1b23653cfbe58>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignSelfTest.html
|
||||
*/
|
||||
|
||||
test("align_self_center", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setAlignSelf(Align.Center);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(45);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(45);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("align_self_flex_end", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setAlignSelf(Align.FlexEnd);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(90);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("align_self_flex_start", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setAlignSelf(Align.FlexStart);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(90);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("align_self_flex_end_override_flex_start", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setAlignItems(Align.FlexStart);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setAlignSelf(Align.FlexEnd);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(90);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("align_self_baseline", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setAlignSelf(Align.Baseline);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setAlignSelf(Align.Baseline);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(20);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child1_child0 = Yoga.Node.create(config);
|
||||
root_child1_child0.setWidth(50);
|
||||
root_child1_child0.setHeight(10);
|
||||
root_child1.insertChild(root_child1_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(50);
|
||||
expect(root_child1.getComputedTop()).toBe(40);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(20);
|
||||
|
||||
expect(root_child1_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child1_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child1_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child1_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(40);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(20);
|
||||
|
||||
expect(root_child1_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child1_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child1_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child1_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
459
test/js/bun/yoga/YGAndroidNewsFeed.test.ts
Normal file
459
test/js/bun/yoga/YGAndroidNewsFeed.test.ts
Normal file
@@ -0,0 +1,459 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<e79ac212057a324d3ce2808face5a71c>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAndroidNewsFeed.html
|
||||
*/
|
||||
|
||||
test("android_news_feed", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setAlignContent(Align.Stretch);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(1080);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0.insertChild(root_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0.setAlignItems(Align.FlexStart);
|
||||
root_child0_child0_child0_child0.setMargin(Edge.Start, 36);
|
||||
root_child0_child0_child0_child0.setMargin(Edge.Top, 24);
|
||||
root_child0_child0_child0.insertChild(root_child0_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child0_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0_child0_child0.setWidth(120);
|
||||
root_child0_child0_child0_child0_child0_child0.setHeight(120);
|
||||
root_child0_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0_child1.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0_child1.setFlexShrink(1);
|
||||
root_child0_child0_child0_child0_child1.setMargin(Edge.Right, 36);
|
||||
root_child0_child0_child0_child0_child1.setPadding(Edge.Left, 36);
|
||||
root_child0_child0_child0_child0_child1.setPadding(Edge.Top, 21);
|
||||
root_child0_child0_child0_child0_child1.setPadding(Edge.Right, 36);
|
||||
root_child0_child0_child0_child0_child1.setPadding(Edge.Bottom, 18);
|
||||
root_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child1, 1);
|
||||
|
||||
const root_child0_child0_child0_child0_child1_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0_child1_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child0_child0_child1_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0_child1_child0.setFlexShrink(1);
|
||||
root_child0_child0_child0_child0_child1.insertChild(root_child0_child0_child0_child0_child1_child0, 0);
|
||||
|
||||
const root_child0_child0_child0_child0_child1_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0_child0_child1_child1.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child0_child0_child1_child1.setFlexShrink(1);
|
||||
root_child0_child0_child0_child0_child1.insertChild(root_child0_child0_child0_child0_child1_child1, 1);
|
||||
|
||||
const root_child0_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1.setAlignContent(Align.Stretch);
|
||||
root_child0_child0.insertChild(root_child0_child0_child1, 1);
|
||||
|
||||
const root_child0_child0_child1_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child1_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0.setAlignItems(Align.FlexStart);
|
||||
root_child0_child0_child1_child0.setMargin(Edge.Start, 174);
|
||||
root_child0_child0_child1_child0.setMargin(Edge.Top, 24);
|
||||
root_child0_child0_child1.insertChild(root_child0_child0_child1_child0, 0);
|
||||
|
||||
const root_child0_child0_child1_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child1_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0.insertChild(root_child0_child0_child1_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child1_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0_child0_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0_child0_child0.setWidth(72);
|
||||
root_child0_child0_child1_child0_child0_child0.setHeight(72);
|
||||
root_child0_child0_child1_child0_child0.insertChild(root_child0_child0_child1_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child1_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0_child1.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0_child1.setFlexShrink(1);
|
||||
root_child0_child0_child1_child0_child1.setMargin(Edge.Right, 36);
|
||||
root_child0_child0_child1_child0_child1.setPadding(Edge.Left, 36);
|
||||
root_child0_child0_child1_child0_child1.setPadding(Edge.Top, 21);
|
||||
root_child0_child0_child1_child0_child1.setPadding(Edge.Right, 36);
|
||||
root_child0_child0_child1_child0_child1.setPadding(Edge.Bottom, 18);
|
||||
root_child0_child0_child1_child0.insertChild(root_child0_child0_child1_child0_child1, 1);
|
||||
|
||||
const root_child0_child0_child1_child0_child1_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0_child1_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0_child0_child1_child0_child1_child0.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0_child1_child0.setFlexShrink(1);
|
||||
root_child0_child0_child1_child0_child1.insertChild(root_child0_child0_child1_child0_child1_child0, 0);
|
||||
|
||||
const root_child0_child0_child1_child0_child1_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1_child0_child1_child1.setAlignContent(Align.Stretch);
|
||||
root_child0_child0_child1_child0_child1_child1.setFlexShrink(1);
|
||||
root_child0_child0_child1_child0_child1.insertChild(root_child0_child0_child1_child0_child1_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(1080);
|
||||
expect(root.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0_child0.getComputedHeight()).toBe(144);
|
||||
|
||||
expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child0_child0.getComputedTop()).toBe(24);
|
||||
expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(1044);
|
||||
expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedLeft()).toBe(120);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedHeight()).toBe(39);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1.getComputedTop()).toBe(144);
|
||||
expect(root_child0_child0_child1.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0_child1.getComputedHeight()).toBe(96);
|
||||
|
||||
expect(root_child0_child0_child1_child0.getComputedLeft()).toBe(174);
|
||||
expect(root_child0_child0_child1_child0.getComputedTop()).toBe(24);
|
||||
expect(root_child0_child0_child1_child0.getComputedWidth()).toBe(906);
|
||||
expect(root_child0_child0_child1_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedLeft()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedHeight()).toBe(39);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedHeight()).toBe(0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(1080);
|
||||
expect(root.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(240);
|
||||
|
||||
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0_child0.getComputedHeight()).toBe(144);
|
||||
|
||||
expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0.getComputedTop()).toBe(24);
|
||||
expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(1044);
|
||||
expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedLeft()).toBe(924);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
|
||||
expect(root_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
|
||||
expect(root_child0_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedLeft()).toBe(816);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child0_child0_child1.getComputedHeight()).toBe(39);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child0_child0_child1_child1.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1.getComputedTop()).toBe(144);
|
||||
expect(root_child0_child0_child1.getComputedWidth()).toBe(1080);
|
||||
expect(root_child0_child0_child1.getComputedHeight()).toBe(96);
|
||||
|
||||
expect(root_child0_child0_child1_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0.getComputedTop()).toBe(24);
|
||||
expect(root_child0_child0_child1_child0.getComputedWidth()).toBe(906);
|
||||
expect(root_child0_child0_child1_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedLeft()).toBe(834);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child0_child0.getComputedHeight()).toBe(72);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedLeft()).toBe(726);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedWidth()).toBe(72);
|
||||
expect(root_child0_child0_child1_child0_child1.getComputedHeight()).toBe(39);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedLeft()).toBe(36);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedTop()).toBe(21);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedWidth()).toBe(0);
|
||||
expect(root_child0_child0_child1_child0_child1_child1.getComputedHeight()).toBe(0);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
375
test/js/bun/yoga/YGAspectRatioTest.test.ts
Normal file
375
test/js/bun/yoga/YGAspectRatioTest.test.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<57064c3213fc22e0637ae5768ce6fea5>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAspectRatioTest.html
|
||||
*/
|
||||
|
||||
test.skip("aspect_ratio_does_not_stretch_cross_axis_dim", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(300);
|
||||
root.setHeight(300);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setOverflow(Overflow.Scroll);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setFlexShrink(1);
|
||||
root_child0.setFlexBasis("0%");
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setFlexDirection(FlexDirection.Row);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child0.setFlexGrow(2);
|
||||
root_child0_child0_child0.setFlexShrink(1);
|
||||
root_child0_child0_child0.setFlexBasis("0%");
|
||||
root_child0_child0_child0.setAspectRatio(1 / 1);
|
||||
root_child0_child0.insertChild(root_child0_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child1.setWidth(5);
|
||||
root_child0_child0.insertChild(root_child0_child0_child1, 1);
|
||||
|
||||
const root_child0_child0_child2 = Yoga.Node.create(config);
|
||||
root_child0_child0_child2.setFlexGrow(1);
|
||||
root_child0_child0_child2.setFlexShrink(1);
|
||||
root_child0_child0_child2.setFlexBasis("0%");
|
||||
root_child0_child0.insertChild(root_child0_child0_child2, 2);
|
||||
|
||||
const root_child0_child0_child2_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child2_child0.setFlexGrow(1);
|
||||
root_child0_child0_child2_child0.setFlexShrink(1);
|
||||
root_child0_child0_child2_child0.setFlexBasis("0%");
|
||||
root_child0_child0_child2_child0.setAspectRatio(1 / 1);
|
||||
root_child0_child0_child2.insertChild(root_child0_child0_child2_child0, 0);
|
||||
|
||||
const root_child0_child0_child2_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0_child2_child0_child0.setWidth(5);
|
||||
root_child0_child0_child2_child0.insertChild(root_child0_child0_child2_child0_child0, 0);
|
||||
|
||||
const root_child0_child0_child2_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child0_child2_child0_child1.setFlexGrow(1);
|
||||
root_child0_child0_child2_child0_child1.setFlexShrink(1);
|
||||
root_child0_child0_child2_child0_child1.setFlexBasis("0%");
|
||||
root_child0_child0_child2_child0_child1.setAspectRatio(1 / 1);
|
||||
root_child0_child0_child2_child0.insertChild(root_child0_child0_child2_child0_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(300);
|
||||
expect(root.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(300);
|
||||
expect(root_child0.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(300);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedWidth()).toBe(197);
|
||||
expect(root_child0_child0_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child1.getComputedLeft()).toBe(197);
|
||||
expect(root_child0_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1.getComputedWidth()).toBe(5);
|
||||
expect(root_child0_child0_child1.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2.getComputedLeft()).toBe(202);
|
||||
expect(root_child0_child0_child2.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedWidth()).toBe(5);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedHeight()).toBe(197);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(300);
|
||||
expect(root.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(300);
|
||||
expect(root_child0.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(300);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child0.getComputedLeft()).toBe(103);
|
||||
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child0.getComputedWidth()).toBe(197);
|
||||
expect(root_child0_child0_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child1.getComputedLeft()).toBe(98);
|
||||
expect(root_child0_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child1.getComputedWidth()).toBe(5);
|
||||
expect(root_child0_child0_child1.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2_child0.getComputedHeight()).toBe(197);
|
||||
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedLeft()).toBe(93);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedWidth()).toBe(5);
|
||||
expect(root_child0_child0_child2_child0_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedWidth()).toBe(98);
|
||||
expect(root_child0_child0_child2_child0_child1.getComputedHeight()).toBe(197);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("zero_aspect_ratio_behaves_like_auto", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(300);
|
||||
root.setHeight(300);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setAspectRatio(0 / 1);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(300);
|
||||
expect(root.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(300);
|
||||
expect(root.getComputedHeight()).toBe(300);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(250);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
491
test/js/bun/yoga/YGAutoTest.test.ts
Normal file
491
test/js/bun/yoga/YGAutoTest.test.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<234cdb7f76ac586e034a5b6ca2033fa4>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAutoTest.html
|
||||
*/
|
||||
|
||||
test("auto_width", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth("auto");
|
||||
root.setHeight(50);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setWidth(50);
|
||||
root_child2.setHeight(50);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(150);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(50);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(100);
|
||||
expect(root_child2.getComputedTop()).toBe(0);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(150);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(100);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(50);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(0);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("auto_height", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(50);
|
||||
root.setHeight("auto");
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setWidth(50);
|
||||
root_child2.setHeight(50);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(150);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(100);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(150);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(100);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("auto_flex_basis", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(50);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setWidth(50);
|
||||
root_child2.setHeight(50);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(150);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(100);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(150);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(100);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("auto_position", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(50);
|
||||
root.setHeight(50);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setPositionAuto(Edge.Right);
|
||||
root_child0.setWidth(25);
|
||||
root_child0.setHeight(25);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(25);
|
||||
expect(root_child0.getComputedHeight()).toBe(25);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(25);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(25);
|
||||
expect(root_child0.getComputedHeight()).toBe(25);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("auto_margin", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(50);
|
||||
root.setHeight(50);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setMargin(Edge.Left, "auto");
|
||||
root_child0.setWidth(25);
|
||||
root_child0.setHeight(25);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(25);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(25);
|
||||
expect(root_child0.getComputedHeight()).toBe(25);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(25);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(25);
|
||||
expect(root_child0.getComputedHeight()).toBe(25);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
400
test/js/bun/yoga/YGBorderTest.test.ts
Normal file
400
test/js/bun/yoga/YGBorderTest.test.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<b1aa13bd88fcb494afb2db9bd80c40e0>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGBorderTest.html
|
||||
*/
|
||||
|
||||
test("border_no_size", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setBorder(Edge.Left, 10);
|
||||
root.setBorder(Edge.Top, 10);
|
||||
root.setBorder(Edge.Right, 10);
|
||||
root.setBorder(Edge.Bottom, 10);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(20);
|
||||
expect(root.getComputedHeight()).toBe(20);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(20);
|
||||
expect(root.getComputedHeight()).toBe(20);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("border_container_match_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setBorder(Edge.Left, 10);
|
||||
root.setBorder(Edge.Top, 10);
|
||||
root.setBorder(Edge.Right, 10);
|
||||
root.setBorder(Edge.Bottom, 10);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(30);
|
||||
expect(root.getComputedHeight()).toBe(30);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(30);
|
||||
expect(root.getComputedHeight()).toBe(30);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("border_flex_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setBorder(Edge.Left, 10);
|
||||
root.setBorder(Edge.Top, 10);
|
||||
root.setBorder(Edge.Right, 10);
|
||||
root.setBorder(Edge.Bottom, 10);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setWidth(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(80);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(80);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(80);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("border_stretch_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setBorder(Edge.Left, 10);
|
||||
root.setBorder(Edge.Top, 10);
|
||||
root.setBorder(Edge.Right, 10);
|
||||
root.setBorder(Edge.Bottom, 10);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(80);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(80);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("border_center_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setJustifyContent(Justify.Center);
|
||||
root.setAlignItems(Align.Center);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setBorder(Edge.Start, 10);
|
||||
root.setBorder(Edge.End, 20);
|
||||
root.setBorder(Edge.Bottom, 20);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(40);
|
||||
expect(root_child0.getComputedTop()).toBe(35);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0.getComputedTop()).toBe(35);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
2794
test/js/bun/yoga/YGBoxSizingTest.test.ts
Normal file
2794
test/js/bun/yoga/YGBoxSizingTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
30
test/js/bun/yoga/YGComputedBorderTest.test.ts
Normal file
30
test/js/bun/yoga/YGComputedBorderTest.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("border_start", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
root.setBorder(Yoga.EDGE_START, 10);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(root.getComputedBorder(Yoga.EDGE_LEFT)).toBe(10);
|
||||
expect(root.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(0);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
|
||||
|
||||
expect(root.getComputedBorder(Yoga.EDGE_LEFT)).toBe(0);
|
||||
expect(root.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(10);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
30
test/js/bun/yoga/YGComputedMarginTest.test.ts
Normal file
30
test/js/bun/yoga/YGComputedMarginTest.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("margin_start", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
root.setMargin(Yoga.EDGE_START, `10%`);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(root.getComputedMargin(Yoga.EDGE_LEFT)).toBe(10);
|
||||
expect(root.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(0);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
|
||||
|
||||
expect(root.getComputedMargin(Yoga.EDGE_LEFT)).toBe(0);
|
||||
expect(root.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(10);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
30
test/js/bun/yoga/YGComputedPaddingTest.test.ts
Normal file
30
test/js/bun/yoga/YGComputedPaddingTest.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("padding_start", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
root.setPadding(Yoga.EDGE_START, `10%`);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(root.getComputedPadding(Yoga.EDGE_LEFT)).toBe(10);
|
||||
expect(root.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(0);
|
||||
|
||||
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
|
||||
|
||||
expect(root.getComputedPadding(Yoga.EDGE_LEFT)).toBe(0);
|
||||
expect(root.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(10);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
273
test/js/bun/yoga/YGDimensionTest.test.ts
Normal file
273
test/js/bun/yoga/YGDimensionTest.test.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<719c8f3b9fea672881a47f593f4aabd0>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGDimensionTest.html
|
||||
*/
|
||||
|
||||
test("wrap_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(100);
|
||||
root_child0.setHeight(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("wrap_grandchild", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setWidth(100);
|
||||
root_child0_child0.setHeight(100);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
158
test/js/bun/yoga/YGDirtiedTest.test.ts
Normal file
158
test/js/bun/yoga/YGDirtiedTest.test.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("dirtied", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
let dirtied = 0;
|
||||
root.setDirtiedFunc(() => {
|
||||
dirtied++;
|
||||
});
|
||||
|
||||
// only nodes with a measure function can be marked dirty
|
||||
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
|
||||
expect(dirtied).toBe(0);
|
||||
|
||||
// dirtied func MUST be called in case of explicit dirtying.
|
||||
root.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
// dirtied func MUST be called ONCE.
|
||||
root.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
|
||||
test("dirtied_propagation", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create();
|
||||
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(20);
|
||||
root_child0.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create();
|
||||
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(20);
|
||||
root.insertChild(root_child1, 0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
let dirtied = 0;
|
||||
root.setDirtiedFunc(() => {
|
||||
dirtied++;
|
||||
});
|
||||
|
||||
expect(dirtied).toBe(0);
|
||||
|
||||
// dirtied func MUST be called for the first time.
|
||||
root_child0.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
// dirtied func must NOT be called for the second time.
|
||||
root_child0.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
|
||||
test("dirtied_hierarchy", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create();
|
||||
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(20);
|
||||
root_child0.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create();
|
||||
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(20);
|
||||
root_child1.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
root.insertChild(root_child1, 0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
let dirtied = 0;
|
||||
root_child0.setDirtiedFunc(() => {
|
||||
dirtied++;
|
||||
});
|
||||
|
||||
expect(dirtied).toBe(0);
|
||||
|
||||
// dirtied func must NOT be called for descendants.
|
||||
// NOTE: nodes without a measure function cannot be marked dirty manually,
|
||||
// but nodes with a measure function can not have children.
|
||||
// Update the width to dirty the node instead.
|
||||
root.setWidth(110);
|
||||
expect(dirtied).toBe(0);
|
||||
|
||||
// dirtied func MUST be called in case of explicit dirtying.
|
||||
root_child0.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
|
||||
test("dirtied_reset", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
let dirtied = 0;
|
||||
root.setDirtiedFunc(() => {
|
||||
dirtied++;
|
||||
});
|
||||
|
||||
expect(dirtied).toBe(0);
|
||||
|
||||
// dirtied func MUST be called in case of explicit dirtying.
|
||||
root.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
// recalculate so the root is no longer dirty
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
root.reset();
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
|
||||
|
||||
root.markDirty();
|
||||
|
||||
// dirtied func must NOT be called after reset.
|
||||
root.markDirty();
|
||||
expect(dirtied).toBe(1);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
253
test/js/bun/yoga/YGDisplayContentsTest.test.ts
Normal file
253
test/js/bun/yoga/YGDisplayContentsTest.test.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<64d58fa2d33230f4eafc9ecbb6012a0d>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGDisplayContentsTest.html
|
||||
*/
|
||||
|
||||
test("test1", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setDisplay(Display.Contents);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setFlexGrow(1);
|
||||
root_child0_child0.setFlexShrink(1);
|
||||
root_child0_child0.setFlexBasis("0%");
|
||||
root_child0_child0.setHeight(10);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
|
||||
const root_child0_child1 = Yoga.Node.create(config);
|
||||
root_child0_child1.setFlexGrow(1);
|
||||
root_child0_child1.setFlexShrink(1);
|
||||
root_child0_child1.setFlexBasis("0%");
|
||||
root_child0_child1.setHeight(20);
|
||||
root_child0.insertChild(root_child0_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
expect(root_child0_child1.getComputedLeft()).toBe(50);
|
||||
expect(root_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child0_child1.getComputedHeight()).toBe(20);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(0);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
expect(root_child0_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child0_child1.getComputedHeight()).toBe(20);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
1158
test/js/bun/yoga/YGDisplayTest.test.ts
Normal file
1158
test/js/bun/yoga/YGDisplayTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
46
test/js/bun/yoga/YGErrataTest.test.ts
Normal file
46
test/js/bun/yoga/YGErrataTest.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("errata_all_contains_example_errata", () => {
|
||||
const config = Yoga.Config.create();
|
||||
try {
|
||||
config.setErrata(Yoga.ERRATA_ALL);
|
||||
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
|
||||
expect(config.getErrata() & Yoga.ERRATA_STRETCH_FLEX_BASIS).not.toBe(0);
|
||||
} finally {
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
|
||||
test("errata_none_omits_example_errata", () => {
|
||||
const config = Yoga.Config.create();
|
||||
try {
|
||||
config.setErrata(Yoga.ERRATA_NONE);
|
||||
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
|
||||
expect(config.getErrata() & Yoga.ERRATA_STRETCH_FLEX_BASIS).toBe(0);
|
||||
} finally {
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
|
||||
test("errata_is_settable", () => {
|
||||
const config = Yoga.Config.create();
|
||||
try {
|
||||
config.setErrata(Yoga.ERRATA_ALL);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_NONE);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
|
||||
} finally {
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
24
test/js/bun/yoga/YGFlexBasisAuto.test.ts
Normal file
24
test/js/bun/yoga/YGFlexBasisAuto.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("flex_basis_auto", () => {
|
||||
const root = Yoga.Node.create();
|
||||
|
||||
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_AUTO);
|
||||
|
||||
root.setFlexBasis(10);
|
||||
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_POINT);
|
||||
expect(root.getFlexBasis().value).toBe(10);
|
||||
|
||||
root.setFlexBasisAuto();
|
||||
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_AUTO);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
4643
test/js/bun/yoga/YGFlexDirectionTest.test.ts
Normal file
4643
test/js/bun/yoga/YGFlexDirectionTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
738
test/js/bun/yoga/YGFlexTest.test.ts
Normal file
738
test/js/bun/yoga/YGFlexTest.test.ts
Normal file
@@ -0,0 +1,738 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
test("flex_basis_flex_grow_column", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setFlexBasis(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexGrow(1);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(75);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(75);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(25);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(75);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(75);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(25);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_shrink_flex_grow_row", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(500);
|
||||
root.setHeight(500);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexShrink(1);
|
||||
root_child0.setWidth(500);
|
||||
root_child0.setHeight(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexShrink(1);
|
||||
root_child1.setWidth(500);
|
||||
root_child1.setHeight(100);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(500);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(250);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(250);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(250);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(500);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(250);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(250);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(250);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_shrink_flex_grow_child_flex_shrink_other_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(500);
|
||||
root.setHeight(500);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexShrink(1);
|
||||
root_child0.setWidth(500);
|
||||
root_child0.setHeight(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexGrow(1);
|
||||
root_child1.setFlexShrink(1);
|
||||
root_child1.setWidth(500);
|
||||
root_child1.setHeight(100);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(500);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(250);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(250);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(250);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(500);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(250);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(250);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(250);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_basis_flex_grow_row", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setFlexBasis(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexGrow(1);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(75);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(75);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(25);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(25);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(75);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(25);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_basis_flex_shrink_column", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexShrink(1);
|
||||
root_child0.setFlexBasis(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexBasis(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_basis_flex_shrink_row", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setFlexDirection(FlexDirection.Row);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexShrink(1);
|
||||
root_child0.setFlexBasis(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexBasis(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(50);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(0);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_shrink_to_zero", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setHeight(75);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexShrink(1);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(50);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setWidth(50);
|
||||
root_child2.setHeight(50);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(75);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(50);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(50);
|
||||
expect(root.getComputedHeight()).toBe(75);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(50);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(50);
|
||||
expect(root_child1.getComputedWidth()).toBe(50);
|
||||
expect(root_child1.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(50);
|
||||
expect(root_child2.getComputedWidth()).toBe(50);
|
||||
expect(root_child2.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_basis_overrides_main_size", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setFlexBasis(50);
|
||||
root_child0.setHeight(20);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexGrow(1);
|
||||
root_child1.setHeight(10);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setFlexGrow(1);
|
||||
root_child2.setHeight(10);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(60);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(60);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(20);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(80);
|
||||
expect(root_child2.getComputedWidth()).toBe(100);
|
||||
expect(root_child2.getComputedHeight()).toBe(20);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(60);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(60);
|
||||
expect(root_child1.getComputedWidth()).toBe(100);
|
||||
expect(root_child1.getComputedHeight()).toBe(20);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(80);
|
||||
expect(root_child2.getComputedWidth()).toBe(100);
|
||||
expect(root_child2.getComputedHeight()).toBe(20);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_grow_shrink_at_most", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setFlexGrow(1);
|
||||
root_child0_child0.setFlexShrink(1);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(0);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("flex_grow_less_than_factor_one", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(200);
|
||||
root.setHeight(500);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(0.2);
|
||||
root_child0.setFlexBasis(40);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create(config);
|
||||
root_child1.setFlexGrow(0.2);
|
||||
root.insertChild(root_child1, 1);
|
||||
|
||||
const root_child2 = Yoga.Node.create(config);
|
||||
root_child2.setFlexGrow(0.4);
|
||||
root.insertChild(root_child2, 2);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0.getComputedHeight()).toBe(132);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(132);
|
||||
expect(root_child1.getComputedWidth()).toBe(200);
|
||||
expect(root_child1.getComputedHeight()).toBe(92);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(224);
|
||||
expect(root_child2.getComputedWidth()).toBe(200);
|
||||
expect(root_child2.getComputedHeight()).toBe(184);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(500);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0.getComputedHeight()).toBe(132);
|
||||
|
||||
expect(root_child1.getComputedLeft()).toBe(0);
|
||||
expect(root_child1.getComputedTop()).toBe(132);
|
||||
expect(root_child1.getComputedWidth()).toBe(200);
|
||||
expect(root_child1.getComputedHeight()).toBe(92);
|
||||
|
||||
expect(root_child2.getComputedLeft()).toBe(0);
|
||||
expect(root_child2.getComputedTop()).toBe(224);
|
||||
expect(root_child2.getComputedWidth()).toBe(200);
|
||||
expect(root_child2.getComputedHeight()).toBe(184);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
2152
test/js/bun/yoga/YGFlexWrapTest.test.ts
Normal file
2152
test/js/bun/yoga/YGFlexWrapTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
3384
test/js/bun/yoga/YGGapTest.test.ts
Normal file
3384
test/js/bun/yoga/YGGapTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
106
test/js/bun/yoga/YGHasNewLayout.test.ts
Normal file
106
test/js/bun/yoga/YGHasNewLayout.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("new_layout_can_be_marked_seen", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.markLayoutSeen();
|
||||
expect(root.hasNewLayout()).toBe(false);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
|
||||
test("new_layout_calculating_layout_marks_layout_as_unseen", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.markLayoutSeen();
|
||||
root.calculateLayout(undefined, undefined);
|
||||
expect(root.hasNewLayout()).toBe(true);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
|
||||
test("new_layout_calculated_layout_can_be_marked_seen", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.calculateLayout(undefined, undefined);
|
||||
root.markLayoutSeen();
|
||||
expect(root.hasNewLayout()).toBe(false);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
|
||||
test("new_layout_recalculating_layout_does_mark_as_unseen", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.calculateLayout(undefined, undefined);
|
||||
root.markLayoutSeen();
|
||||
root.calculateLayout(undefined, undefined);
|
||||
expect(root.hasNewLayout()).toBe(true);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
|
||||
test("new_layout_reset_also_resets_layout_seen", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.markLayoutSeen();
|
||||
root.reset();
|
||||
expect(root.hasNewLayout()).toBe(true);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
|
||||
test("new_layout_children_sets_new_layout", () => {
|
||||
const root = Yoga.Node.create();
|
||||
try {
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create();
|
||||
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child0.setWidth(50);
|
||||
root_child0.setHeight(20);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child1 = Yoga.Node.create();
|
||||
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root_child1.setWidth(50);
|
||||
root_child1.setHeight(20);
|
||||
root.insertChild(root_child1, 0);
|
||||
|
||||
expect(root.hasNewLayout()).toEqual(true);
|
||||
expect(root_child0.hasNewLayout()).toEqual(true);
|
||||
expect(root_child1.hasNewLayout()).toEqual(true);
|
||||
|
||||
root.markLayoutSeen();
|
||||
root_child0.markLayoutSeen();
|
||||
root_child1.markLayoutSeen();
|
||||
|
||||
expect(root.hasNewLayout()).toEqual(false);
|
||||
expect(root_child0.hasNewLayout()).toEqual(false);
|
||||
expect(root_child1.hasNewLayout()).toEqual(false);
|
||||
|
||||
root_child1.setHeight(30);
|
||||
root.calculateLayout(undefined, undefined);
|
||||
|
||||
expect(root.hasNewLayout()).toEqual(true);
|
||||
expect(root_child0.hasNewLayout()).toEqual(true);
|
||||
expect(root_child1.hasNewLayout()).toEqual(true);
|
||||
} finally {
|
||||
root.freeRecursive();
|
||||
}
|
||||
});
|
||||
3389
test/js/bun/yoga/YGIntrinsicSizeTest.test.ts
Normal file
3389
test/js/bun/yoga/YGIntrinsicSizeTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
2231
test/js/bun/yoga/YGJustifyContentTest.test.ts
Normal file
2231
test/js/bun/yoga/YGJustifyContentTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
2010
test/js/bun/yoga/YGMarginTest.test.ts
Normal file
2010
test/js/bun/yoga/YGMarginTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
167
test/js/bun/yoga/YGMeasureCacheTest.test.ts
Normal file
167
test/js/bun/yoga/YGMeasureCacheTest.test.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
type MeasureCounter = {
|
||||
inc: (width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number };
|
||||
get: () => number;
|
||||
};
|
||||
|
||||
function getMeasureCounter(
|
||||
cb?:
|
||||
| ((width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number })
|
||||
| null,
|
||||
staticWidth = 0,
|
||||
staticHeight = 0,
|
||||
): MeasureCounter {
|
||||
let counter = 0;
|
||||
|
||||
return {
|
||||
inc: function (width: number, widthMode: number, height: number, heightMode: number) {
|
||||
counter += 1;
|
||||
|
||||
return cb ? cb(width, widthMode, height, heightMode) : { width: staticWidth, height: staticHeight };
|
||||
},
|
||||
|
||||
get: function () {
|
||||
return counter;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getMeasureCounterMax(): MeasureCounter {
|
||||
return getMeasureCounter((width, widthMode, height, heightMode) => {
|
||||
const measuredWidth = widthMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : width;
|
||||
const measuredHeight = heightMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : height;
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
});
|
||||
}
|
||||
|
||||
function getMeasureCounterMin(): MeasureCounter {
|
||||
return getMeasureCounter((width, widthMode, height, heightMode) => {
|
||||
const measuredWidth =
|
||||
widthMode === Yoga.MEASURE_MODE_UNDEFINED || (widthMode == Yoga.MEASURE_MODE_AT_MOST && width > 10) ? 10 : width;
|
||||
const measuredHeight =
|
||||
heightMode === Yoga.MEASURE_MODE_UNDEFINED || (heightMode == Yoga.MEASURE_MODE_AT_MOST && height > 10)
|
||||
? 10
|
||||
: height;
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("measure_once_single_flexible_child", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
root.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const measureCounter = getMeasureCounterMax();
|
||||
|
||||
const root_child0 = Yoga.Node.create();
|
||||
root_child0.setMeasureFunc(measureCounter.inc);
|
||||
root_child0.setFlexGrow(1);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(measureCounter.get()).toBe(1);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
207
test/js/bun/yoga/YGMeasureTest.test.ts
Normal file
207
test/js/bun/yoga/YGMeasureTest.test.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
type MeasureCounter = {
|
||||
inc: (width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number };
|
||||
get: () => number;
|
||||
};
|
||||
|
||||
function getMeasureCounter(
|
||||
cb?:
|
||||
| ((width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number })
|
||||
| null,
|
||||
staticWidth = 0,
|
||||
staticHeight = 0,
|
||||
): MeasureCounter {
|
||||
let counter = 0;
|
||||
|
||||
return {
|
||||
inc: function (width: number, widthMode: number, height: number, heightMode: number) {
|
||||
counter += 1;
|
||||
|
||||
return cb ? cb(width, widthMode, height, heightMode) : { width: staticWidth, height: staticHeight };
|
||||
},
|
||||
|
||||
get: function () {
|
||||
return counter;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getMeasureCounterMax(): MeasureCounter {
|
||||
return getMeasureCounter((width, widthMode, height, heightMode) => {
|
||||
const measuredWidth = widthMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : width;
|
||||
const measuredHeight = heightMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : height;
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
});
|
||||
}
|
||||
|
||||
function getMeasureCounterMin(): MeasureCounter {
|
||||
return getMeasureCounter((width, widthMode, height, heightMode) => {
|
||||
const measuredWidth =
|
||||
widthMode === Yoga.MEASURE_MODE_UNDEFINED || (widthMode == Yoga.MEASURE_MODE_AT_MOST && width > 10) ? 10 : width;
|
||||
const measuredHeight =
|
||||
heightMode === Yoga.MEASURE_MODE_UNDEFINED || (heightMode == Yoga.MEASURE_MODE_AT_MOST && height > 10)
|
||||
? 10
|
||||
: height;
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
test("dont_measure_single_grow_shrink_child", () => {
|
||||
const root = Yoga.Node.create();
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const measureCounter = getMeasureCounter(null, 100, 100);
|
||||
|
||||
const root_child0 = Yoga.Node.create();
|
||||
root_child0.setMeasureFunc(measureCounter.inc);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setFlexShrink(1);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(measureCounter.get()).toBe(0);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
|
||||
test("dont_fail_with_incomplete_measure_dimensions", () => {
|
||||
// @ts-expect-error Testing bad usage
|
||||
const heightOnlyCallback = getMeasureCounter(() => ({ height: 10 }));
|
||||
// @ts-expect-error Testing bad usage
|
||||
const widthOnlyCallback = getMeasureCounter(() => ({ width: 10 }));
|
||||
// @ts-expect-error Testing bad usage
|
||||
const emptyCallback = getMeasureCounter(() => ({}));
|
||||
|
||||
const root = Yoga.Node.create();
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const node1 = Yoga.Node.create();
|
||||
const node2 = Yoga.Node.create();
|
||||
const node3 = Yoga.Node.create();
|
||||
|
||||
root.insertChild(node1, root.getChildCount());
|
||||
root.insertChild(node2, root.getChildCount());
|
||||
root.insertChild(node3, root.getChildCount());
|
||||
|
||||
node1.setMeasureFunc(heightOnlyCallback.inc);
|
||||
node2.setMeasureFunc(widthOnlyCallback.inc);
|
||||
node3.setMeasureFunc(emptyCallback.inc);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
|
||||
|
||||
expect(heightOnlyCallback.get()).toBe(1);
|
||||
expect(widthOnlyCallback.get()).toBe(1);
|
||||
expect(emptyCallback.get()).toBe(1);
|
||||
|
||||
expect(node1.getComputedWidth()).toBe(100);
|
||||
expect(node1.getComputedHeight()).toBe(10);
|
||||
|
||||
expect(node2.getComputedWidth()).toBe(100);
|
||||
expect(node2.getComputedHeight()).toBe(0);
|
||||
|
||||
expect(node3.getComputedWidth()).toBe(100);
|
||||
expect(node3.getComputedHeight()).toBe(0);
|
||||
|
||||
root.freeRecursive();
|
||||
});
|
||||
1507
test/js/bun/yoga/YGMinMaxDimensionTest.test.ts
Normal file
1507
test/js/bun/yoga/YGMinMaxDimensionTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
498
test/js/bun/yoga/YGPaddingTest.test.ts
Normal file
498
test/js/bun/yoga/YGPaddingTest.test.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<07e65137c3055db0090d46067ec69d5e>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGPaddingTest.html
|
||||
*/
|
||||
|
||||
test("padding_no_size", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Left, 10);
|
||||
root.setPadding(Edge.Top, 10);
|
||||
root.setPadding(Edge.Right, 10);
|
||||
root.setPadding(Edge.Bottom, 10);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(20);
|
||||
expect(root.getComputedHeight()).toBe(20);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(20);
|
||||
expect(root.getComputedHeight()).toBe(20);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("padding_container_match_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Left, 10);
|
||||
root.setPadding(Edge.Top, 10);
|
||||
root.setPadding(Edge.Right, 10);
|
||||
root.setPadding(Edge.Bottom, 10);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(30);
|
||||
expect(root.getComputedHeight()).toBe(30);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(30);
|
||||
expect(root.getComputedHeight()).toBe(30);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("padding_flex_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Left, 10);
|
||||
root.setPadding(Edge.Top, 10);
|
||||
root.setPadding(Edge.Right, 10);
|
||||
root.setPadding(Edge.Bottom, 10);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setFlexGrow(1);
|
||||
root_child0.setWidth(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(80);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(80);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(80);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("padding_stretch_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Left, 10);
|
||||
root.setPadding(Edge.Top, 10);
|
||||
root.setPadding(Edge.Right, 10);
|
||||
root.setPadding(Edge.Bottom, 10);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(80);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(10);
|
||||
expect(root_child0.getComputedTop()).toBe(10);
|
||||
expect(root_child0.getComputedWidth()).toBe(80);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("padding_center_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setJustifyContent(Justify.Center);
|
||||
root.setAlignItems(Align.Center);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Start, 10);
|
||||
root.setPadding(Edge.End, 20);
|
||||
root.setPadding(Edge.Bottom, 20);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(10);
|
||||
root_child0.setHeight(10);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(40);
|
||||
expect(root_child0.getComputedTop()).toBe(35);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0.getComputedTop()).toBe(35);
|
||||
expect(root_child0.getComputedWidth()).toBe(10);
|
||||
expect(root_child0.getComputedHeight()).toBe(10);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("child_with_padding_align_end", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setJustifyContent(Justify.FlexEnd);
|
||||
root.setAlignItems(Align.FlexEnd);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(200);
|
||||
root.setHeight(200);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setPadding(Edge.Left, 20);
|
||||
root_child0.setPadding(Edge.Top, 20);
|
||||
root_child0.setPadding(Edge.Right, 20);
|
||||
root_child0.setPadding(Edge.Bottom, 20);
|
||||
root_child0.setWidth(100);
|
||||
root_child0.setHeight(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(100);
|
||||
expect(root_child0.getComputedTop()).toBe(100);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(100);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("physical_and_relative_edge_defined", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setPadding(Edge.Left, 20);
|
||||
root.setPadding(Edge.End, 50);
|
||||
root.setWidth(200);
|
||||
root.setHeight(200);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth("100%");
|
||||
root_child0.setHeight(50);
|
||||
root.insertChild(root_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(20);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(130);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(200);
|
||||
expect(root.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(50);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(150);
|
||||
expect(root_child0.getComputedHeight()).toBe(50);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
1821
test/js/bun/yoga/YGPercentageTest.test.ts
Normal file
1821
test/js/bun/yoga/YGPercentageTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
1222
test/js/bun/yoga/YGRoundingTest.test.ts
Normal file
1222
test/js/bun/yoga/YGRoundingTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
351
test/js/bun/yoga/YGSizeOverflowTest.test.ts
Normal file
351
test/js/bun/yoga/YGSizeOverflowTest.test.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
import { expect, test } from "bun:test";
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
const Align = {
|
||||
Center: Yoga.ALIGN_CENTER,
|
||||
FlexEnd: Yoga.ALIGN_FLEX_END,
|
||||
FlexStart: Yoga.ALIGN_FLEX_START,
|
||||
Stretch: Yoga.ALIGN_STRETCH,
|
||||
Baseline: Yoga.ALIGN_BASELINE,
|
||||
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
|
||||
Auto: Yoga.ALIGN_AUTO,
|
||||
};
|
||||
|
||||
const BoxSizing = {
|
||||
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
|
||||
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
|
||||
};
|
||||
|
||||
const Direction = {
|
||||
LTR: Yoga.DIRECTION_LTR,
|
||||
RTL: Yoga.DIRECTION_RTL,
|
||||
Inherit: Yoga.DIRECTION_INHERIT,
|
||||
};
|
||||
|
||||
const Display = {
|
||||
Flex: Yoga.DISPLAY_FLEX,
|
||||
None: Yoga.DISPLAY_NONE,
|
||||
Contents: Yoga.DISPLAY_CONTENTS,
|
||||
};
|
||||
|
||||
const Edge = {
|
||||
Left: Yoga.EDGE_LEFT,
|
||||
Top: Yoga.EDGE_TOP,
|
||||
Right: Yoga.EDGE_RIGHT,
|
||||
Bottom: Yoga.EDGE_BOTTOM,
|
||||
Start: Yoga.EDGE_START,
|
||||
End: Yoga.EDGE_END,
|
||||
Horizontal: Yoga.EDGE_HORIZONTAL,
|
||||
Vertical: Yoga.EDGE_VERTICAL,
|
||||
All: Yoga.EDGE_ALL,
|
||||
};
|
||||
|
||||
const FlexDirection = {
|
||||
Column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
Row: Yoga.FLEX_DIRECTION_ROW,
|
||||
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
const Justify = {
|
||||
FlexStart: Yoga.JUSTIFY_FLEX_START,
|
||||
Center: Yoga.JUSTIFY_CENTER,
|
||||
FlexEnd: Yoga.JUSTIFY_FLEX_END,
|
||||
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
|
||||
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
const MeasureMode = {
|
||||
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
|
||||
Exactly: Yoga.MEASURE_MODE_EXACTLY,
|
||||
AtMost: Yoga.MEASURE_MODE_AT_MOST,
|
||||
};
|
||||
|
||||
const Overflow = {
|
||||
Visible: Yoga.OVERFLOW_VISIBLE,
|
||||
Hidden: Yoga.OVERFLOW_HIDDEN,
|
||||
Scroll: Yoga.OVERFLOW_SCROLL,
|
||||
};
|
||||
|
||||
const PositionType = {
|
||||
Static: Yoga.POSITION_TYPE_STATIC,
|
||||
Relative: Yoga.POSITION_TYPE_RELATIVE,
|
||||
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
|
||||
};
|
||||
|
||||
const Unit = {
|
||||
Undefined: Yoga.UNIT_UNDEFINED,
|
||||
Point: Yoga.UNIT_POINT,
|
||||
Percent: Yoga.UNIT_PERCENT,
|
||||
Auto: Yoga.UNIT_AUTO,
|
||||
};
|
||||
|
||||
const Wrap = {
|
||||
NoWrap: Yoga.WRAP_NO_WRAP,
|
||||
Wrap: Yoga.WRAP_WRAP,
|
||||
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
function instrinsicSizeMeasureFunc(
|
||||
this: { text: string; flexDirection: number },
|
||||
width: number,
|
||||
widthMode: number,
|
||||
height: number,
|
||||
heightMode: number,
|
||||
): { width: number; height: number } {
|
||||
const textLength = this.text.length;
|
||||
const words = this.text.split(" ");
|
||||
const flexDirection = this.flexDirection;
|
||||
const widthPerChar = 10;
|
||||
const heightPerChar = 10;
|
||||
|
||||
let measuredWidth: number;
|
||||
let measuredHeight: number;
|
||||
|
||||
switch (widthMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredWidth = width;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredWidth = Math.min(width, textLength * widthPerChar);
|
||||
break;
|
||||
default:
|
||||
measuredWidth = textLength * widthPerChar;
|
||||
}
|
||||
|
||||
switch (heightMode) {
|
||||
case MeasureMode.Exactly:
|
||||
measuredHeight = height;
|
||||
break;
|
||||
case MeasureMode.AtMost:
|
||||
measuredHeight = Math.min(height, calculateHeight());
|
||||
break;
|
||||
default:
|
||||
measuredHeight = calculateHeight();
|
||||
}
|
||||
|
||||
function longestWordWidth() {
|
||||
return Math.max(...words.map(word => word.length)) * widthPerChar;
|
||||
}
|
||||
|
||||
function calculateHeight() {
|
||||
if (textLength * widthPerChar <= measuredWidth) {
|
||||
return heightPerChar;
|
||||
}
|
||||
|
||||
const maxLineWidth =
|
||||
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
|
||||
|
||||
let lines = 1;
|
||||
let currentLineLength = 0;
|
||||
for (const word of words) {
|
||||
const wordWidth = word.length * widthPerChar;
|
||||
if (wordWidth > maxLineWidth) {
|
||||
if (currentLineLength > 0) {
|
||||
lines++;
|
||||
}
|
||||
lines++;
|
||||
currentLineLength = 0;
|
||||
} else if (currentLineLength + wordWidth <= maxLineWidth) {
|
||||
currentLineLength += widthPerChar + wordWidth;
|
||||
} else {
|
||||
lines++;
|
||||
currentLineLength = widthPerChar + wordWidth;
|
||||
}
|
||||
}
|
||||
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
|
||||
}
|
||||
|
||||
return { width: measuredWidth, height: measuredHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @generated SignedSource<<d4b41aec2c4130f06b3ee9e76d70d944>>
|
||||
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGSizeOverflowTest.html
|
||||
*/
|
||||
|
||||
test("nested_overflowing_child", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setWidth(200);
|
||||
root_child0_child0.setHeight(200);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(-100);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("nested_overflowing_child_in_constraint_parent", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(100);
|
||||
root_child0.setHeight(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setWidth(200);
|
||||
root_child0_child0.setHeight(200);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(-100);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(200);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
test("parent_wrap_child_size_overflowing_parent", () => {
|
||||
const config = Yoga.Config.create();
|
||||
let root;
|
||||
|
||||
try {
|
||||
root = Yoga.Node.create(config);
|
||||
root.setPositionType(PositionType.Absolute);
|
||||
root.setWidth(100);
|
||||
root.setHeight(100);
|
||||
|
||||
const root_child0 = Yoga.Node.create(config);
|
||||
root_child0.setWidth(100);
|
||||
root.insertChild(root_child0, 0);
|
||||
|
||||
const root_child0_child0 = Yoga.Node.create(config);
|
||||
root_child0_child0.setWidth(100);
|
||||
root_child0_child0.setHeight(200);
|
||||
root_child0.insertChild(root_child0_child0, 0);
|
||||
root.calculateLayout(undefined, undefined, Direction.LTR);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
root.calculateLayout(undefined, undefined, Direction.RTL);
|
||||
|
||||
expect(root.getComputedLeft()).toBe(0);
|
||||
expect(root.getComputedTop()).toBe(0);
|
||||
expect(root.getComputedWidth()).toBe(100);
|
||||
expect(root.getComputedHeight()).toBe(100);
|
||||
|
||||
expect(root_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0.getComputedHeight()).toBe(200);
|
||||
|
||||
expect(root_child0_child0.getComputedLeft()).toBe(0);
|
||||
expect(root_child0_child0.getComputedTop()).toBe(0);
|
||||
expect(root_child0_child0.getComputedWidth()).toBe(100);
|
||||
expect(root_child0_child0.getComputedHeight()).toBe(200);
|
||||
} finally {
|
||||
if (typeof root !== "undefined") {
|
||||
root.freeRecursive();
|
||||
}
|
||||
|
||||
config.free();
|
||||
}
|
||||
});
|
||||
6419
test/js/bun/yoga/YGStaticPositionTest.test.ts
Normal file
6419
test/js/bun/yoga/YGStaticPositionTest.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
102
test/js/bun/yoga/yoga-config.test.ts
Normal file
102
test/js/bun/yoga/yoga-config.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
// Test if we can access Yoga via Bun.Yoga
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
describe("Yoga.Config", () => {
|
||||
test("Config constructor", () => {
|
||||
const config = new Yoga.Config();
|
||||
expect(config).toBeDefined();
|
||||
expect(config.constructor.name).toBe("Config");
|
||||
});
|
||||
|
||||
test("Config.create() static method", () => {
|
||||
const config = Yoga.Config.create();
|
||||
expect(config).toBeDefined();
|
||||
expect(config.constructor.name).toBe("Config");
|
||||
});
|
||||
|
||||
test("setUseWebDefaults", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Should not throw
|
||||
expect(() => config.setUseWebDefaults(true)).not.toThrow();
|
||||
expect(() => config.setUseWebDefaults(false)).not.toThrow();
|
||||
expect(() => config.setUseWebDefaults()).not.toThrow(); // defaults to true
|
||||
});
|
||||
|
||||
test("useWebDefaults (legacy)", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Should not throw
|
||||
expect(() => config.useWebDefaults()).not.toThrow();
|
||||
});
|
||||
|
||||
test("setPointScaleFactor and getPointScaleFactor", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
config.setPointScaleFactor(2.0);
|
||||
expect(config.getPointScaleFactor()).toBe(2.0);
|
||||
|
||||
config.setPointScaleFactor(0); // disable pixel rounding
|
||||
expect(config.getPointScaleFactor()).toBe(0);
|
||||
|
||||
config.setPointScaleFactor(3.5);
|
||||
expect(config.getPointScaleFactor()).toBe(3.5);
|
||||
});
|
||||
|
||||
test("setErrata and getErrata", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Test with different errata values
|
||||
config.setErrata(Yoga.ERRATA_NONE);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_CLASSIC);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_CLASSIC);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_ALL);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
|
||||
});
|
||||
|
||||
test("setExperimentalFeatureEnabled and isExperimentalFeatureEnabled", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Test with a hypothetical experimental feature
|
||||
const feature = 0; // Assuming 0 is a valid experimental feature
|
||||
|
||||
config.setExperimentalFeatureEnabled(feature, true);
|
||||
expect(config.isExperimentalFeatureEnabled(feature)).toBe(true);
|
||||
|
||||
config.setExperimentalFeatureEnabled(feature, false);
|
||||
expect(config.isExperimentalFeatureEnabled(feature)).toBe(false);
|
||||
});
|
||||
|
||||
test("isEnabledForNodes", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Should return true for a valid config
|
||||
expect(config.isEnabledForNodes()).toBe(true);
|
||||
});
|
||||
|
||||
test("free", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Should not throw
|
||||
expect(() => config.free()).not.toThrow();
|
||||
|
||||
// After free, double free should throw an error (this is correct behavior)
|
||||
expect(() => config.free()).toThrow("Cannot perform operation on freed Yoga.Config");
|
||||
});
|
||||
|
||||
test("error handling", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Test invalid arguments
|
||||
expect(() => config.setErrata()).toThrow();
|
||||
expect(() => config.setExperimentalFeatureEnabled()).toThrow();
|
||||
expect(() => config.setExperimentalFeatureEnabled(0)).toThrow(); // missing second arg
|
||||
expect(() => config.isExperimentalFeatureEnabled()).toThrow();
|
||||
expect(() => config.setPointScaleFactor()).toThrow();
|
||||
});
|
||||
});
|
||||
119
test/js/bun/yoga/yoga-constants.test.ts
Normal file
119
test/js/bun/yoga/yoga-constants.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
// Test if we can access Yoga via Bun.Yoga
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
describe("Yoga Constants", () => {
|
||||
test("should export all alignment constants", () => {
|
||||
expect(Yoga.ALIGN_AUTO).toBeDefined();
|
||||
expect(Yoga.ALIGN_FLEX_START).toBeDefined();
|
||||
expect(Yoga.ALIGN_CENTER).toBeDefined();
|
||||
expect(Yoga.ALIGN_FLEX_END).toBeDefined();
|
||||
expect(Yoga.ALIGN_STRETCH).toBeDefined();
|
||||
expect(Yoga.ALIGN_BASELINE).toBeDefined();
|
||||
expect(Yoga.ALIGN_SPACE_BETWEEN).toBeDefined();
|
||||
expect(Yoga.ALIGN_SPACE_AROUND).toBeDefined();
|
||||
expect(Yoga.ALIGN_SPACE_EVENLY).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all direction constants", () => {
|
||||
expect(Yoga.DIRECTION_INHERIT).toBeDefined();
|
||||
expect(Yoga.DIRECTION_LTR).toBeDefined();
|
||||
expect(Yoga.DIRECTION_RTL).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all display constants", () => {
|
||||
expect(Yoga.DISPLAY_FLEX).toBeDefined();
|
||||
expect(Yoga.DISPLAY_NONE).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all edge constants", () => {
|
||||
expect(Yoga.EDGE_LEFT).toBeDefined();
|
||||
expect(Yoga.EDGE_TOP).toBeDefined();
|
||||
expect(Yoga.EDGE_RIGHT).toBeDefined();
|
||||
expect(Yoga.EDGE_BOTTOM).toBeDefined();
|
||||
expect(Yoga.EDGE_START).toBeDefined();
|
||||
expect(Yoga.EDGE_END).toBeDefined();
|
||||
expect(Yoga.EDGE_HORIZONTAL).toBeDefined();
|
||||
expect(Yoga.EDGE_VERTICAL).toBeDefined();
|
||||
expect(Yoga.EDGE_ALL).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all experimental feature constants", () => {
|
||||
expect(Yoga.EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all flex direction constants", () => {
|
||||
expect(Yoga.FLEX_DIRECTION_COLUMN).toBeDefined();
|
||||
expect(Yoga.FLEX_DIRECTION_COLUMN_REVERSE).toBeDefined();
|
||||
expect(Yoga.FLEX_DIRECTION_ROW).toBeDefined();
|
||||
expect(Yoga.FLEX_DIRECTION_ROW_REVERSE).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all gutter constants", () => {
|
||||
expect(Yoga.GUTTER_COLUMN).toBeDefined();
|
||||
expect(Yoga.GUTTER_ROW).toBeDefined();
|
||||
expect(Yoga.GUTTER_ALL).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all justify constants", () => {
|
||||
expect(Yoga.JUSTIFY_FLEX_START).toBeDefined();
|
||||
expect(Yoga.JUSTIFY_CENTER).toBeDefined();
|
||||
expect(Yoga.JUSTIFY_FLEX_END).toBeDefined();
|
||||
expect(Yoga.JUSTIFY_SPACE_BETWEEN).toBeDefined();
|
||||
expect(Yoga.JUSTIFY_SPACE_AROUND).toBeDefined();
|
||||
expect(Yoga.JUSTIFY_SPACE_EVENLY).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all measure mode constants", () => {
|
||||
expect(Yoga.MEASURE_MODE_UNDEFINED).toBeDefined();
|
||||
expect(Yoga.MEASURE_MODE_EXACTLY).toBeDefined();
|
||||
expect(Yoga.MEASURE_MODE_AT_MOST).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all node type constants", () => {
|
||||
expect(Yoga.NODE_TYPE_DEFAULT).toBeDefined();
|
||||
expect(Yoga.NODE_TYPE_TEXT).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all overflow constants", () => {
|
||||
expect(Yoga.OVERFLOW_VISIBLE).toBeDefined();
|
||||
expect(Yoga.OVERFLOW_HIDDEN).toBeDefined();
|
||||
expect(Yoga.OVERFLOW_SCROLL).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all position type constants", () => {
|
||||
expect(Yoga.POSITION_TYPE_STATIC).toBeDefined();
|
||||
expect(Yoga.POSITION_TYPE_RELATIVE).toBeDefined();
|
||||
expect(Yoga.POSITION_TYPE_ABSOLUTE).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all unit constants", () => {
|
||||
expect(Yoga.UNIT_UNDEFINED).toBeDefined();
|
||||
expect(Yoga.UNIT_POINT).toBeDefined();
|
||||
expect(Yoga.UNIT_PERCENT).toBeDefined();
|
||||
expect(Yoga.UNIT_AUTO).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all wrap constants", () => {
|
||||
expect(Yoga.WRAP_NO_WRAP).toBeDefined();
|
||||
expect(Yoga.WRAP_WRAP).toBeDefined();
|
||||
expect(Yoga.WRAP_WRAP_REVERSE).toBeDefined();
|
||||
});
|
||||
|
||||
test("should export all errata constants", () => {
|
||||
expect(Yoga.ERRATA_NONE).toBeDefined();
|
||||
expect(Yoga.ERRATA_STRETCH_FLEX_BASIS).toBeDefined();
|
||||
expect(Yoga.ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING).toBeDefined();
|
||||
expect(Yoga.ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE).toBeDefined();
|
||||
expect(Yoga.ERRATA_ALL).toBeDefined();
|
||||
expect(Yoga.ERRATA_CLASSIC).toBeDefined();
|
||||
});
|
||||
|
||||
test("constants should have correct numeric values", () => {
|
||||
// Check a few key constants have reasonable values
|
||||
expect(typeof Yoga.EDGE_TOP).toBe("number");
|
||||
expect(typeof Yoga.UNIT_PERCENT).toBe("number");
|
||||
expect(typeof Yoga.FLEX_DIRECTION_ROW).toBe("number");
|
||||
});
|
||||
});
|
||||
304
test/js/bun/yoga/yoga-layout-comprehensive.test.ts
Normal file
304
test/js/bun/yoga/yoga-layout-comprehensive.test.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
describe("Yoga - Comprehensive Layout Tests", () => {
|
||||
test("basic flexbox row layout with flex grow", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
container.setWidth(300);
|
||||
container.setHeight(100);
|
||||
|
||||
const child1 = new Yoga.Node();
|
||||
child1.setFlex(1);
|
||||
|
||||
const child2 = new Yoga.Node();
|
||||
child2.setFlex(2);
|
||||
|
||||
const child3 = new Yoga.Node();
|
||||
child3.setWidth(50); // Fixed width
|
||||
|
||||
container.insertChild(child1, 0);
|
||||
container.insertChild(child2, 1);
|
||||
container.insertChild(child3, 2);
|
||||
|
||||
container.calculateLayout();
|
||||
|
||||
// Verify container layout
|
||||
const containerLayout = container.getComputedLayout();
|
||||
expect(containerLayout.width).toBe(300);
|
||||
expect(containerLayout.height).toBe(100);
|
||||
|
||||
// Verify children layout
|
||||
// Available space: 300 - 50 (fixed width) = 250
|
||||
// child1 gets 1/3 of 250 = ~83.33
|
||||
// child2 gets 2/3 of 250 = ~166.67
|
||||
// child3 gets fixed 50
|
||||
const child1Layout = child1.getComputedLayout();
|
||||
const child2Layout = child2.getComputedLayout();
|
||||
const child3Layout = child3.getComputedLayout();
|
||||
|
||||
expect(child1Layout.left).toBe(0);
|
||||
expect(child1Layout.width).toBe(83);
|
||||
expect(child1Layout.height).toBe(100);
|
||||
|
||||
expect(child2Layout.left).toBe(83);
|
||||
expect(child2Layout.width).toBe(167);
|
||||
expect(child2Layout.height).toBe(100);
|
||||
|
||||
expect(child3Layout.left).toBe(250);
|
||||
expect(child3Layout.width).toBe(50);
|
||||
expect(child3Layout.height).toBe(100);
|
||||
});
|
||||
|
||||
test("column layout with justify content and align items", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
container.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
|
||||
container.setAlignItems(Yoga.ALIGN_CENTER);
|
||||
container.setWidth(200);
|
||||
container.setHeight(300);
|
||||
|
||||
const child1 = new Yoga.Node();
|
||||
child1.setWidth(50);
|
||||
child1.setHeight(50);
|
||||
|
||||
const child2 = new Yoga.Node();
|
||||
child2.setWidth(80);
|
||||
child2.setHeight(60);
|
||||
|
||||
const child3 = new Yoga.Node();
|
||||
child3.setWidth(30);
|
||||
child3.setHeight(40);
|
||||
|
||||
container.insertChild(child1, 0);
|
||||
container.insertChild(child2, 1);
|
||||
container.insertChild(child3, 2);
|
||||
|
||||
container.calculateLayout();
|
||||
|
||||
const child1Layout = child1.getComputedLayout();
|
||||
const child2Layout = child2.getComputedLayout();
|
||||
const child3Layout = child3.getComputedLayout();
|
||||
|
||||
// Verify vertical spacing (JUSTIFY_SPACE_BETWEEN)
|
||||
// Total child height: 50 + 60 + 40 = 150
|
||||
// Available space: 300 - 150 = 150
|
||||
// Space between: 150 / 2 = 75
|
||||
expect(child1Layout.top).toBe(0);
|
||||
expect(child2Layout.top).toBe(125); // 50 + 75
|
||||
expect(child3Layout.top).toBe(260); // 50 + 75 + 60 + 75
|
||||
|
||||
// Verify horizontal centering (ALIGN_CENTER)
|
||||
expect(child1Layout.left).toBe(75); // (200 - 50) / 2
|
||||
expect(child2Layout.left).toBe(60); // (200 - 80) / 2
|
||||
expect(child3Layout.left).toBe(85); // (200 - 30) / 2
|
||||
});
|
||||
|
||||
test("nested flexbox layout", () => {
|
||||
const outerContainer = new Yoga.Node();
|
||||
outerContainer.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
outerContainer.setWidth(400);
|
||||
outerContainer.setHeight(200);
|
||||
|
||||
const leftPanel = new Yoga.Node();
|
||||
leftPanel.setWidth(100);
|
||||
|
||||
const rightPanel = new Yoga.Node();
|
||||
rightPanel.setFlex(1);
|
||||
rightPanel.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
||||
|
||||
const topSection = new Yoga.Node();
|
||||
topSection.setFlex(1);
|
||||
|
||||
const bottomSection = new Yoga.Node();
|
||||
bottomSection.setHeight(50);
|
||||
|
||||
outerContainer.insertChild(leftPanel, 0);
|
||||
outerContainer.insertChild(rightPanel, 1);
|
||||
rightPanel.insertChild(topSection, 0);
|
||||
rightPanel.insertChild(bottomSection, 1);
|
||||
|
||||
outerContainer.calculateLayout();
|
||||
|
||||
const leftLayout = leftPanel.getComputedLayout();
|
||||
const rightLayout = rightPanel.getComputedLayout();
|
||||
const topLayout = topSection.getComputedLayout();
|
||||
const bottomLayout = bottomSection.getComputedLayout();
|
||||
|
||||
// Left panel
|
||||
expect(leftLayout.left).toBe(0);
|
||||
expect(leftLayout.width).toBe(100);
|
||||
expect(leftLayout.height).toBe(200);
|
||||
|
||||
// Right panel
|
||||
expect(rightLayout.left).toBe(100);
|
||||
expect(rightLayout.width).toBe(300); // 400 - 100
|
||||
expect(rightLayout.height).toBe(200);
|
||||
|
||||
// Top section of right panel
|
||||
expect(topLayout.left).toBe(0); // Relative to right panel
|
||||
expect(topLayout.top).toBe(0);
|
||||
expect(topLayout.width).toBe(300);
|
||||
expect(topLayout.height).toBe(150); // 200 - 50
|
||||
|
||||
// Bottom section of right panel
|
||||
expect(bottomLayout.left).toBe(0);
|
||||
expect(bottomLayout.top).toBe(150);
|
||||
expect(bottomLayout.width).toBe(300);
|
||||
expect(bottomLayout.height).toBe(50);
|
||||
});
|
||||
|
||||
test("flex wrap with multiple lines", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
container.setFlexWrap(Yoga.WRAP_WRAP);
|
||||
container.setWidth(200);
|
||||
container.setHeight(200);
|
||||
|
||||
// Create children that will overflow and wrap
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const child = new Yoga.Node();
|
||||
child.setWidth(80);
|
||||
child.setHeight(50);
|
||||
container.insertChild(child, i);
|
||||
}
|
||||
|
||||
container.calculateLayout();
|
||||
|
||||
// First line: child 0, 1 (80 + 80 = 160, fits in 200)
|
||||
// Second line: child 2, 3 (80 + 80 = 160, fits in 200)
|
||||
// Third line: child 4 (80, fits in 200)
|
||||
|
||||
const child0Layout = container.getChild(0).getComputedLayout();
|
||||
const child1Layout = container.getChild(1).getComputedLayout();
|
||||
const child2Layout = container.getChild(2).getComputedLayout();
|
||||
const child3Layout = container.getChild(3).getComputedLayout();
|
||||
const child4Layout = container.getChild(4).getComputedLayout();
|
||||
|
||||
// First line
|
||||
expect(child0Layout.top).toBe(0);
|
||||
expect(child0Layout.left).toBe(0);
|
||||
expect(child1Layout.top).toBe(0);
|
||||
expect(child1Layout.left).toBe(80);
|
||||
|
||||
// Second line
|
||||
expect(child2Layout.top).toBe(50);
|
||||
expect(child2Layout.left).toBe(0);
|
||||
expect(child3Layout.top).toBe(50);
|
||||
expect(child3Layout.left).toBe(80);
|
||||
|
||||
// Third line
|
||||
expect(child4Layout.top).toBe(100);
|
||||
expect(child4Layout.left).toBe(0);
|
||||
});
|
||||
|
||||
test("margin and padding calculations", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setPadding(Yoga.EDGE_ALL, 10);
|
||||
container.setWidth(200);
|
||||
container.setHeight(150);
|
||||
|
||||
const child = new Yoga.Node();
|
||||
child.setMargin(Yoga.EDGE_ALL, 15);
|
||||
child.setFlex(1);
|
||||
|
||||
container.insertChild(child, 0);
|
||||
container.calculateLayout();
|
||||
|
||||
const containerLayout = container.getComputedLayout();
|
||||
const childLayout = child.getComputedLayout();
|
||||
|
||||
// Container should maintain its size
|
||||
expect(containerLayout.width).toBe(200);
|
||||
expect(containerLayout.height).toBe(150);
|
||||
|
||||
// Child should account for container padding and its own margin
|
||||
// Available width: 200 - (10+10 padding) - (15+15 margin) = 150
|
||||
// Available height: 150 - (10+10 padding) - (15+15 margin) = 100
|
||||
expect(childLayout.left).toBe(25); // container padding + child margin
|
||||
expect(childLayout.top).toBe(25);
|
||||
expect(childLayout.width).toBe(150);
|
||||
expect(childLayout.height).toBe(100);
|
||||
});
|
||||
|
||||
test("percentage-based dimensions", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setWidth(400);
|
||||
container.setHeight(300);
|
||||
|
||||
const child = new Yoga.Node();
|
||||
child.setWidth("50%"); // 50% of 400 = 200
|
||||
child.setHeight("75%"); // 75% of 300 = 225
|
||||
|
||||
container.insertChild(child, 0);
|
||||
container.calculateLayout();
|
||||
|
||||
const childLayout = child.getComputedLayout();
|
||||
expect(childLayout.width).toBe(200);
|
||||
expect(childLayout.height).toBe(225);
|
||||
});
|
||||
|
||||
test("min/max constraints", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
container.setWidth(500);
|
||||
container.setHeight(100);
|
||||
|
||||
const child1 = new Yoga.Node();
|
||||
child1.setFlex(1);
|
||||
child1.setMinWidth(100);
|
||||
child1.setMaxWidth(200);
|
||||
|
||||
const child2 = new Yoga.Node();
|
||||
child2.setFlex(2);
|
||||
|
||||
container.insertChild(child1, 0);
|
||||
container.insertChild(child2, 1);
|
||||
container.calculateLayout();
|
||||
|
||||
const child1Layout = child1.getComputedLayout();
|
||||
const child2Layout = child2.getComputedLayout();
|
||||
|
||||
// child1 would normally get 1/3 of 500 = ~166.67
|
||||
// But it's clamped by maxWidth(200), so it gets 200
|
||||
expect(child1Layout.width).toBe(200);
|
||||
|
||||
// child2 gets the remaining space: 500 - 200 = 300
|
||||
expect(child2Layout.width).toBe(300);
|
||||
});
|
||||
|
||||
test("absolute positioning", () => {
|
||||
const container = new Yoga.Node();
|
||||
container.setWidth(300);
|
||||
container.setHeight(200);
|
||||
|
||||
const normalChild = new Yoga.Node();
|
||||
normalChild.setWidth(100);
|
||||
normalChild.setHeight(50);
|
||||
|
||||
const absoluteChild = new Yoga.Node();
|
||||
absoluteChild.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
|
||||
absoluteChild.setPosition(Yoga.EDGE_TOP, 20);
|
||||
absoluteChild.setPosition(Yoga.EDGE_LEFT, 50);
|
||||
absoluteChild.setWidth(80);
|
||||
absoluteChild.setHeight(60);
|
||||
|
||||
container.insertChild(normalChild, 0);
|
||||
container.insertChild(absoluteChild, 1);
|
||||
container.calculateLayout();
|
||||
|
||||
const normalLayout = normalChild.getComputedLayout();
|
||||
const absoluteLayout = absoluteChild.getComputedLayout();
|
||||
|
||||
// Normal child positioned normally
|
||||
expect(normalLayout.left).toBe(0);
|
||||
expect(normalLayout.top).toBe(0);
|
||||
|
||||
// Absolute child positioned absolutely
|
||||
expect(absoluteLayout.left).toBe(50);
|
||||
expect(absoluteLayout.top).toBe(20);
|
||||
expect(absoluteLayout.width).toBe(80);
|
||||
expect(absoluteLayout.height).toBe(60);
|
||||
});
|
||||
});
|
||||
791
test/js/bun/yoga/yoga-node-extended.test.ts
Normal file
791
test/js/bun/yoga/yoga-node-extended.test.ts
Normal file
@@ -0,0 +1,791 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
describe("Yoga.Node - Extended Tests", () => {
|
||||
describe("Node creation and cloning", () => {
|
||||
test("clone() creates independent copy", () => {
|
||||
const original = new Yoga.Node();
|
||||
original.setWidth(100);
|
||||
original.setHeight(200);
|
||||
original.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
|
||||
const cloned = original.clone();
|
||||
expect(cloned).toBeDefined();
|
||||
expect(cloned).not.toBe(original);
|
||||
|
||||
// Verify cloned has same properties
|
||||
const originalWidth = original.getWidth();
|
||||
const clonedWidth = cloned.getWidth();
|
||||
expect(clonedWidth.value).toBe(originalWidth.value);
|
||||
expect(clonedWidth.unit).toBe(originalWidth.unit);
|
||||
|
||||
// Verify they're independent
|
||||
original.setWidth(300);
|
||||
expect(cloned.getWidth().value).toBe(100);
|
||||
});
|
||||
|
||||
test("clone() preserves measure function", () => {
|
||||
const original = new Yoga.Node();
|
||||
let originalMeasureCalled = false;
|
||||
let clonedMeasureCalled = false;
|
||||
|
||||
original.setMeasureFunc((width, height) => {
|
||||
originalMeasureCalled = true;
|
||||
return { width: 100, height: 50 };
|
||||
});
|
||||
|
||||
const cloned = original.clone();
|
||||
|
||||
// Both should have measure functions
|
||||
original.markDirty();
|
||||
original.calculateLayout();
|
||||
expect(originalMeasureCalled).toBe(true);
|
||||
|
||||
// Note: cloned nodes share the same measure function reference
|
||||
cloned.markDirty();
|
||||
cloned.calculateLayout();
|
||||
// The original measure function is called again
|
||||
expect(originalMeasureCalled).toBe(true);
|
||||
});
|
||||
|
||||
test("clone() with hierarchy", () => {
|
||||
const parent = new Yoga.Node();
|
||||
const child1 = new Yoga.Node();
|
||||
const child2 = new Yoga.Node();
|
||||
|
||||
parent.insertChild(child1, 0);
|
||||
parent.insertChild(child2, 1);
|
||||
|
||||
const clonedParent = parent.clone();
|
||||
expect(clonedParent.getChildCount()).toBe(2);
|
||||
|
||||
const clonedChild1 = clonedParent.getChild(0);
|
||||
const clonedChild2 = clonedParent.getChild(1);
|
||||
|
||||
expect(clonedChild1).toBeDefined();
|
||||
expect(clonedChild2).toBeDefined();
|
||||
expect(clonedChild1).not.toBe(child1);
|
||||
expect(clonedChild2).not.toBe(child2);
|
||||
});
|
||||
|
||||
test("copyStyle() copies style properties", () => {
|
||||
const source = new Yoga.Node();
|
||||
source.setWidth(100);
|
||||
source.setHeight(200);
|
||||
source.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
source.setJustifyContent(Yoga.JUSTIFY_CENTER);
|
||||
source.setAlignItems(Yoga.ALIGN_CENTER);
|
||||
|
||||
const target = new Yoga.Node();
|
||||
target.copyStyle(source);
|
||||
|
||||
expect(target.getWidth()).toEqual(source.getWidth());
|
||||
expect(target.getHeight()).toEqual(source.getHeight());
|
||||
// Note: Can't verify flex direction directly as getter is not accessible
|
||||
});
|
||||
|
||||
test("freeRecursive() frees node and children", () => {
|
||||
const parent = new Yoga.Node();
|
||||
const child1 = new Yoga.Node();
|
||||
const child2 = new Yoga.Node();
|
||||
const grandchild = new Yoga.Node();
|
||||
|
||||
parent.insertChild(child1, 0);
|
||||
parent.insertChild(child2, 1);
|
||||
child1.insertChild(grandchild, 0);
|
||||
|
||||
expect(() => parent.freeRecursive()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Direction and layout", () => {
|
||||
test("setDirection/getDirection", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setDirection(Yoga.DIRECTION_LTR);
|
||||
expect(node.getDirection()).toBe(Yoga.DIRECTION_LTR);
|
||||
|
||||
node.setDirection(Yoga.DIRECTION_RTL);
|
||||
expect(node.getDirection()).toBe(Yoga.DIRECTION_RTL);
|
||||
|
||||
node.setDirection(Yoga.DIRECTION_INHERIT);
|
||||
expect(node.getDirection()).toBe(Yoga.DIRECTION_INHERIT);
|
||||
});
|
||||
|
||||
test("getComputedLeft/Top/Width/Height", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setWidth(100);
|
||||
node.setHeight(100);
|
||||
node.calculateLayout();
|
||||
|
||||
expect(node.getComputedLeft()).toBe(0);
|
||||
expect(node.getComputedTop()).toBe(0);
|
||||
expect(node.getComputedWidth()).toBe(100);
|
||||
expect(node.getComputedHeight()).toBe(100);
|
||||
});
|
||||
|
||||
test("getComputedRight/Bottom calculations", () => {
|
||||
const parent = new Yoga.Node();
|
||||
parent.setWidth(200);
|
||||
parent.setHeight(200);
|
||||
|
||||
const child = new Yoga.Node();
|
||||
child.setWidth(100);
|
||||
child.setHeight(100);
|
||||
child.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
|
||||
child.setPosition(Yoga.EDGE_LEFT, 10);
|
||||
child.setPosition(Yoga.EDGE_TOP, 20);
|
||||
|
||||
parent.insertChild(child, 0);
|
||||
parent.calculateLayout();
|
||||
|
||||
expect(child.getComputedLeft()).toBe(10);
|
||||
expect(child.getComputedTop()).toBe(20);
|
||||
// Yoga's getComputedRight/Bottom return position offsets, not absolute coordinates
|
||||
// Since we positioned with left/top, right/bottom will be the original position values
|
||||
expect(child.getComputedRight()).toBe(10);
|
||||
expect(child.getComputedBottom()).toBe(20);
|
||||
});
|
||||
|
||||
test("getComputedMargin", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setMargin(Yoga.EDGE_TOP, 10);
|
||||
node.setMargin(Yoga.EDGE_RIGHT, 20);
|
||||
node.setMargin(Yoga.EDGE_BOTTOM, 30);
|
||||
node.setMargin(Yoga.EDGE_LEFT, 40);
|
||||
node.setWidth(100);
|
||||
node.setHeight(100);
|
||||
|
||||
const parent = new Yoga.Node();
|
||||
parent.setWidth(300);
|
||||
parent.setHeight(300);
|
||||
parent.insertChild(node, 0);
|
||||
parent.calculateLayout();
|
||||
|
||||
expect(node.getComputedMargin(Yoga.EDGE_TOP)).toBe(10);
|
||||
expect(node.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(20);
|
||||
expect(node.getComputedMargin(Yoga.EDGE_BOTTOM)).toBe(30);
|
||||
expect(node.getComputedMargin(Yoga.EDGE_LEFT)).toBe(40);
|
||||
});
|
||||
|
||||
test("getComputedPadding", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setPadding(Yoga.EDGE_ALL, 15);
|
||||
node.setWidth(100);
|
||||
node.setHeight(100);
|
||||
node.calculateLayout();
|
||||
|
||||
expect(node.getComputedPadding(Yoga.EDGE_TOP)).toBe(15);
|
||||
expect(node.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(15);
|
||||
expect(node.getComputedPadding(Yoga.EDGE_BOTTOM)).toBe(15);
|
||||
expect(node.getComputedPadding(Yoga.EDGE_LEFT)).toBe(15);
|
||||
});
|
||||
|
||||
test("getComputedBorder", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setBorder(Yoga.EDGE_ALL, 5);
|
||||
node.setWidth(100);
|
||||
node.setHeight(100);
|
||||
node.calculateLayout();
|
||||
|
||||
expect(node.getComputedBorder(Yoga.EDGE_TOP)).toBe(5);
|
||||
expect(node.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(5);
|
||||
expect(node.getComputedBorder(Yoga.EDGE_BOTTOM)).toBe(5);
|
||||
expect(node.getComputedBorder(Yoga.EDGE_LEFT)).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Flexbox properties", () => {
|
||||
test("setAlignContent/getAlignContent", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setAlignContent(Yoga.ALIGN_FLEX_START);
|
||||
expect(node.getAlignContent()).toBe(Yoga.ALIGN_FLEX_START);
|
||||
|
||||
node.setAlignContent(Yoga.ALIGN_CENTER);
|
||||
expect(node.getAlignContent()).toBe(Yoga.ALIGN_CENTER);
|
||||
|
||||
node.setAlignContent(Yoga.ALIGN_STRETCH);
|
||||
expect(node.getAlignContent()).toBe(Yoga.ALIGN_STRETCH);
|
||||
});
|
||||
|
||||
test("setAlignSelf/getAlignSelf", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setAlignSelf(Yoga.ALIGN_AUTO);
|
||||
expect(node.getAlignSelf()).toBe(Yoga.ALIGN_AUTO);
|
||||
|
||||
node.setAlignSelf(Yoga.ALIGN_FLEX_END);
|
||||
expect(node.getAlignSelf()).toBe(Yoga.ALIGN_FLEX_END);
|
||||
});
|
||||
|
||||
test("setAlignItems/getAlignItems", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setAlignItems(Yoga.ALIGN_FLEX_START);
|
||||
expect(node.getAlignItems()).toBe(Yoga.ALIGN_FLEX_START);
|
||||
|
||||
node.setAlignItems(Yoga.ALIGN_BASELINE);
|
||||
expect(node.getAlignItems()).toBe(Yoga.ALIGN_BASELINE);
|
||||
});
|
||||
|
||||
test("getFlex", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setFlex(2.5);
|
||||
expect(node.getFlex()).toBe(2.5);
|
||||
|
||||
node.setFlex(0);
|
||||
expect(node.getFlex()).toBe(0);
|
||||
});
|
||||
|
||||
test("setFlexWrap/getFlexWrap", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setFlexWrap(Yoga.WRAP_NO_WRAP);
|
||||
expect(node.getFlexWrap()).toBe(Yoga.WRAP_NO_WRAP);
|
||||
|
||||
node.setFlexWrap(Yoga.WRAP_WRAP);
|
||||
expect(node.getFlexWrap()).toBe(Yoga.WRAP_WRAP);
|
||||
|
||||
node.setFlexWrap(Yoga.WRAP_WRAP_REVERSE);
|
||||
expect(node.getFlexWrap()).toBe(Yoga.WRAP_WRAP_REVERSE);
|
||||
});
|
||||
|
||||
test("getFlexDirection", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
expect(node.getFlexDirection()).toBe(Yoga.FLEX_DIRECTION_ROW);
|
||||
|
||||
node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
|
||||
expect(node.getFlexDirection()).toBe(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
|
||||
});
|
||||
|
||||
test("getFlexGrow/getFlexShrink", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setFlexGrow(2);
|
||||
expect(node.getFlexGrow()).toBe(2);
|
||||
|
||||
node.setFlexShrink(0.5);
|
||||
expect(node.getFlexShrink()).toBe(0.5);
|
||||
});
|
||||
|
||||
test("getJustifyContent", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
|
||||
expect(node.getJustifyContent()).toBe(Yoga.JUSTIFY_SPACE_BETWEEN);
|
||||
|
||||
node.setJustifyContent(Yoga.JUSTIFY_SPACE_AROUND);
|
||||
expect(node.getJustifyContent()).toBe(Yoga.JUSTIFY_SPACE_AROUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Position properties", () => {
|
||||
test("setPosition/getPosition", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setPosition(Yoga.EDGE_LEFT, 10);
|
||||
expect(node.getPosition(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
|
||||
|
||||
node.setPosition(Yoga.EDGE_TOP, "20%");
|
||||
expect(node.getPosition(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 20 });
|
||||
|
||||
node.setPosition(Yoga.EDGE_RIGHT, { unit: Yoga.UNIT_POINT, value: 30 });
|
||||
expect(node.getPosition(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 30 });
|
||||
});
|
||||
|
||||
test("setPositionType/getPositionType", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
|
||||
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_ABSOLUTE);
|
||||
|
||||
node.setPositionType(Yoga.POSITION_TYPE_RELATIVE);
|
||||
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_RELATIVE);
|
||||
|
||||
node.setPositionType(Yoga.POSITION_TYPE_STATIC);
|
||||
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_STATIC);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Size properties", () => {
|
||||
test("height/width with percentage", () => {
|
||||
const parent = new Yoga.Node();
|
||||
parent.setWidth(200);
|
||||
parent.setHeight(200);
|
||||
|
||||
const child = new Yoga.Node();
|
||||
child.setWidth("50%");
|
||||
child.setHeight("75%");
|
||||
|
||||
parent.insertChild(child, 0);
|
||||
parent.calculateLayout();
|
||||
|
||||
expect(child.getComputedWidth()).toBe(100); // 50% of 200
|
||||
expect(child.getComputedHeight()).toBe(150); // 75% of 200
|
||||
});
|
||||
|
||||
test("getAspectRatio", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setAspectRatio(1.5);
|
||||
expect(node.getAspectRatio()).toBe(1.5);
|
||||
|
||||
node.setAspectRatio(undefined);
|
||||
expect(node.getAspectRatio()).toBeNaN();
|
||||
});
|
||||
|
||||
test("size constraints affect layout", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setMinWidth(50);
|
||||
node.setMinHeight(50);
|
||||
node.setMaxWidth(100);
|
||||
node.setMaxHeight(100);
|
||||
|
||||
// Width/height beyond constraints
|
||||
node.setWidth(200);
|
||||
node.setHeight(200);
|
||||
|
||||
node.calculateLayout();
|
||||
|
||||
// Constraints are now working correctly - values should be clamped to max
|
||||
expect(node.getComputedWidth()).toBe(100);
|
||||
expect(node.getComputedHeight()).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Spacing properties", () => {
|
||||
test("setPadding/getPadding", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Set padding on individual edges
|
||||
node.setPadding(Yoga.EDGE_TOP, 10);
|
||||
node.setPadding(Yoga.EDGE_RIGHT, 10);
|
||||
node.setPadding(Yoga.EDGE_BOTTOM, 10);
|
||||
node.setPadding(Yoga.EDGE_LEFT, 10);
|
||||
|
||||
expect(node.getPadding(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
|
||||
expect(node.getPadding(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
|
||||
|
||||
// Set different values
|
||||
node.setPadding(Yoga.EDGE_LEFT, 20);
|
||||
node.setPadding(Yoga.EDGE_RIGHT, 20);
|
||||
expect(node.getPadding(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 20 });
|
||||
expect(node.getPadding(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 20 });
|
||||
|
||||
node.setPadding(Yoga.EDGE_TOP, "15%");
|
||||
expect(node.getPadding(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 15 });
|
||||
});
|
||||
|
||||
test("setBorder/getBorder", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Set border on individual edges
|
||||
node.setBorder(Yoga.EDGE_TOP, 5);
|
||||
node.setBorder(Yoga.EDGE_RIGHT, 5);
|
||||
node.setBorder(Yoga.EDGE_BOTTOM, 5);
|
||||
node.setBorder(Yoga.EDGE_LEFT, 5);
|
||||
|
||||
expect(node.getBorder(Yoga.EDGE_TOP)).toBe(5);
|
||||
expect(node.getBorder(Yoga.EDGE_RIGHT)).toBe(5);
|
||||
|
||||
node.setBorder(Yoga.EDGE_TOP, 10);
|
||||
expect(node.getBorder(Yoga.EDGE_TOP)).toBe(10);
|
||||
expect(node.getBorder(Yoga.EDGE_RIGHT)).toBe(5); // Should still be 5
|
||||
});
|
||||
|
||||
test("getGap with different gutters", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setGap(Yoga.GUTTER_ROW, 10);
|
||||
expect(node.getGap(Yoga.GUTTER_ROW)).toEqual({ value: 10, unit: Yoga.UNIT_POINT });
|
||||
|
||||
node.setGap(Yoga.GUTTER_COLUMN, 20);
|
||||
expect(node.getGap(Yoga.GUTTER_COLUMN)).toEqual({ value: 20, unit: Yoga.UNIT_POINT });
|
||||
|
||||
// Verify row and column gaps are independent
|
||||
expect(node.getGap(Yoga.GUTTER_ROW)).toEqual({ value: 10, unit: Yoga.UNIT_POINT });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Node type and display", () => {
|
||||
test("setNodeType/getNodeType", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_DEFAULT);
|
||||
|
||||
node.setNodeType(Yoga.NODE_TYPE_TEXT);
|
||||
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_TEXT);
|
||||
|
||||
node.setNodeType(Yoga.NODE_TYPE_DEFAULT);
|
||||
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_DEFAULT);
|
||||
});
|
||||
|
||||
test("setDisplay/getDisplay", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setDisplay(Yoga.DISPLAY_FLEX);
|
||||
expect(node.getDisplay()).toBe(Yoga.DISPLAY_FLEX);
|
||||
|
||||
node.setDisplay(Yoga.DISPLAY_NONE);
|
||||
expect(node.getDisplay()).toBe(Yoga.DISPLAY_NONE);
|
||||
});
|
||||
|
||||
test("setOverflow/getOverflow", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setOverflow(Yoga.OVERFLOW_HIDDEN);
|
||||
expect(node.getOverflow()).toBe(Yoga.OVERFLOW_HIDDEN);
|
||||
|
||||
node.setOverflow(Yoga.OVERFLOW_SCROLL);
|
||||
expect(node.getOverflow()).toBe(Yoga.OVERFLOW_SCROLL);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Box sizing", () => {
|
||||
test("setBoxSizing/getBoxSizing", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Default is border-box
|
||||
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_BORDER_BOX);
|
||||
|
||||
node.setBoxSizing(Yoga.BOX_SIZING_CONTENT_BOX);
|
||||
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_CONTENT_BOX);
|
||||
|
||||
node.setBoxSizing(Yoga.BOX_SIZING_BORDER_BOX);
|
||||
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_BORDER_BOX);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Layout state", () => {
|
||||
test("setHasNewLayout/getHasNewLayout", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.calculateLayout();
|
||||
expect(node.getHasNewLayout()).toBe(true);
|
||||
|
||||
node.setHasNewLayout(false);
|
||||
expect(node.getHasNewLayout()).toBe(false);
|
||||
|
||||
node.setHasNewLayout(true);
|
||||
expect(node.getHasNewLayout()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Baseline", () => {
|
||||
test("setIsReferenceBaseline/isReferenceBaseline", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(node.isReferenceBaseline()).toBe(false);
|
||||
|
||||
node.setIsReferenceBaseline(true);
|
||||
expect(node.isReferenceBaseline()).toBe(true);
|
||||
|
||||
node.setIsReferenceBaseline(false);
|
||||
expect(node.isReferenceBaseline()).toBe(false);
|
||||
});
|
||||
|
||||
test("setBaselineFunc", () => {
|
||||
const node = new Yoga.Node();
|
||||
let baselineCalled = false;
|
||||
|
||||
node.setBaselineFunc((width, height) => {
|
||||
baselineCalled = true;
|
||||
return height * 0.8;
|
||||
});
|
||||
|
||||
// Set up a scenario where baseline function is called
|
||||
const container = new Yoga.Node();
|
||||
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
container.setAlignItems(Yoga.ALIGN_BASELINE);
|
||||
container.setWidth(300);
|
||||
container.setHeight(100);
|
||||
|
||||
node.setWidth(100);
|
||||
node.setHeight(50);
|
||||
container.insertChild(node, 0);
|
||||
|
||||
// Add another child to trigger baseline alignment
|
||||
const sibling = new Yoga.Node();
|
||||
sibling.setWidth(100);
|
||||
sibling.setHeight(60);
|
||||
container.insertChild(sibling, 1);
|
||||
|
||||
container.calculateLayout();
|
||||
|
||||
// Clear the baseline function
|
||||
node.setBaselineFunc(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hierarchy operations", () => {
|
||||
test("removeAllChildren", () => {
|
||||
const parent = new Yoga.Node();
|
||||
const child1 = new Yoga.Node();
|
||||
const child2 = new Yoga.Node();
|
||||
const child3 = new Yoga.Node();
|
||||
|
||||
parent.insertChild(child1, 0);
|
||||
parent.insertChild(child2, 1);
|
||||
parent.insertChild(child3, 2);
|
||||
|
||||
expect(parent.getChildCount()).toBe(3);
|
||||
|
||||
parent.removeAllChildren();
|
||||
|
||||
expect(parent.getChildCount()).toBe(0);
|
||||
expect(child1.getParent()).toBeNull();
|
||||
expect(child2.getParent()).toBeNull();
|
||||
expect(child3.getParent()).toBeNull();
|
||||
});
|
||||
|
||||
test("getOwner", () => {
|
||||
const parent = new Yoga.Node();
|
||||
const child = new Yoga.Node();
|
||||
|
||||
parent.insertChild(child, 0);
|
||||
|
||||
// getOwner returns the parent node that owns this node
|
||||
expect(child.getOwner()).toBe(parent);
|
||||
|
||||
const clonedParent = parent.clone();
|
||||
const clonedChild = clonedParent.getChild(0);
|
||||
|
||||
// After a deep clone, the cloned child's owner is the cloned parent
|
||||
expect(clonedChild.getOwner()).toBe(clonedParent);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Config association", () => {
|
||||
test("getConfig returns associated config", () => {
|
||||
const config = new Yoga.Config();
|
||||
const node = new Yoga.Node(config);
|
||||
|
||||
expect(node.getConfig()).toBe(config);
|
||||
});
|
||||
|
||||
test("getConfig returns null for nodes without config", () => {
|
||||
const node = new Yoga.Node();
|
||||
expect(node.getConfig()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases and error handling", () => {
|
||||
test("getChild with invalid index", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(node.getChild(-1)).toBeNull();
|
||||
expect(node.getChild(0)).toBeNull();
|
||||
expect(node.getChild(10)).toBeNull();
|
||||
});
|
||||
|
||||
test("getParent for root node", () => {
|
||||
const node = new Yoga.Node();
|
||||
expect(node.getParent()).toBeNull();
|
||||
});
|
||||
|
||||
// TODO: This test currently causes a segmentation fault
|
||||
// Operations on freed nodes should be safe but currently crash
|
||||
// test("operations on freed node", () => {
|
||||
// const node = new Yoga.Node();
|
||||
// node.free();
|
||||
//
|
||||
// // Operations on freed nodes should not crash
|
||||
// expect(() => node.setWidth(100)).not.toThrow();
|
||||
// expect(() => node.getWidth()).not.toThrow();
|
||||
// });
|
||||
|
||||
test("markDirty edge cases", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// markDirty without measure function should throw
|
||||
expect(() => node.markDirty()).toThrow("Only nodes with custom measure functions can be marked as dirty");
|
||||
|
||||
// With measure function it should work
|
||||
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
|
||||
expect(() => node.markDirty()).not.toThrow();
|
||||
});
|
||||
|
||||
test("calculateLayout with various dimensions", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(() => node.calculateLayout()).not.toThrow();
|
||||
expect(() => node.calculateLayout(undefined, undefined)).not.toThrow();
|
||||
expect(() => node.calculateLayout(Yoga.UNDEFINED, Yoga.UNDEFINED)).not.toThrow();
|
||||
expect(() => node.calculateLayout(100, 100, Yoga.DIRECTION_LTR)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Yoga.Config - Extended Tests", () => {
|
||||
test("Config constructor and create", () => {
|
||||
const config1 = new Yoga.Config();
|
||||
expect(config1).toBeDefined();
|
||||
expect(config1.constructor.name).toBe("Config");
|
||||
|
||||
const config2 = Yoga.Config.create();
|
||||
expect(config2).toBeDefined();
|
||||
expect(config2.constructor.name).toBe("Config");
|
||||
});
|
||||
|
||||
test("setUseWebDefaults/getUseWebDefaults", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
expect(config.getUseWebDefaults()).toBe(false);
|
||||
|
||||
config.setUseWebDefaults(true);
|
||||
expect(config.getUseWebDefaults()).toBe(true);
|
||||
|
||||
config.setUseWebDefaults(false);
|
||||
expect(config.getUseWebDefaults()).toBe(false);
|
||||
});
|
||||
|
||||
test("setPointScaleFactor/getPointScaleFactor", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Default is usually 1.0
|
||||
const defaultScale = config.getPointScaleFactor();
|
||||
expect(defaultScale).toBeGreaterThan(0);
|
||||
|
||||
config.setPointScaleFactor(2.0);
|
||||
expect(config.getPointScaleFactor()).toBe(2.0);
|
||||
|
||||
config.setPointScaleFactor(0.0);
|
||||
expect(config.getPointScaleFactor()).toBe(0.0);
|
||||
});
|
||||
|
||||
test("setContext/getContext", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
expect(config.getContext()).toBeNull();
|
||||
|
||||
const context = { foo: "bar", num: 42, arr: [1, 2, 3] };
|
||||
config.setContext(context);
|
||||
expect(config.getContext()).toBe(context);
|
||||
|
||||
config.setContext(null);
|
||||
expect(config.getContext()).toBeNull();
|
||||
});
|
||||
|
||||
test("setLogger callback", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Set logger
|
||||
config.setLogger((config, node, level, format) => {
|
||||
console.log("Logger called");
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Clear logger
|
||||
config.setLogger(null);
|
||||
|
||||
// Setting invalid logger
|
||||
expect(() => config.setLogger("not a function")).toThrow();
|
||||
});
|
||||
|
||||
test("setCloneNodeFunc callback", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Set clone function
|
||||
config.setCloneNodeFunc((oldNode, owner, childIndex) => {
|
||||
return oldNode.clone();
|
||||
});
|
||||
|
||||
// Clear clone function
|
||||
config.setCloneNodeFunc(null);
|
||||
|
||||
// Setting invalid clone function
|
||||
expect(() => config.setCloneNodeFunc("not a function")).toThrow();
|
||||
});
|
||||
|
||||
// TODO: This test currently causes a segmentation fault
|
||||
// Operations on freed configs should be safe but currently crash
|
||||
// test("free config", () => {
|
||||
// const config = new Yoga.Config();
|
||||
// expect(() => config.free()).not.toThrow();
|
||||
//
|
||||
// // Operations after free should not crash
|
||||
// expect(() => config.setPointScaleFactor(2.0)).not.toThrow();
|
||||
// });
|
||||
|
||||
test("setErrata/getErrata", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_CLASSIC);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_CLASSIC);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_ALL);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
|
||||
|
||||
config.setErrata(Yoga.ERRATA_NONE);
|
||||
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
|
||||
});
|
||||
|
||||
test("experimental features", () => {
|
||||
const config = new Yoga.Config();
|
||||
|
||||
// Check if experimental feature methods exist
|
||||
expect(typeof config.setExperimentalFeatureEnabled).toBe("function");
|
||||
expect(typeof config.isExperimentalFeatureEnabled).toBe("function");
|
||||
|
||||
// Try enabling/disabling a feature (0 as example)
|
||||
expect(() => config.setExperimentalFeatureEnabled(0, true)).not.toThrow();
|
||||
expect(() => config.isExperimentalFeatureEnabled(0)).not.toThrow();
|
||||
});
|
||||
|
||||
test("isEnabledForNodes", () => {
|
||||
const config = new Yoga.Config();
|
||||
expect(typeof config.isEnabledForNodes()).toBe("boolean");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Yoga Constants Verification", () => {
|
||||
test("All required constants are defined", () => {
|
||||
// Edge constants
|
||||
expect(typeof Yoga.EDGE_LEFT).toBe("number");
|
||||
expect(typeof Yoga.EDGE_TOP).toBe("number");
|
||||
expect(typeof Yoga.EDGE_RIGHT).toBe("number");
|
||||
expect(typeof Yoga.EDGE_BOTTOM).toBe("number");
|
||||
expect(typeof Yoga.EDGE_START).toBe("number");
|
||||
expect(typeof Yoga.EDGE_END).toBe("number");
|
||||
expect(typeof Yoga.EDGE_HORIZONTAL).toBe("number");
|
||||
expect(typeof Yoga.EDGE_VERTICAL).toBe("number");
|
||||
expect(typeof Yoga.EDGE_ALL).toBe("number");
|
||||
|
||||
// Unit constants
|
||||
expect(typeof Yoga.UNIT_UNDEFINED).toBe("number");
|
||||
expect(typeof Yoga.UNIT_POINT).toBe("number");
|
||||
expect(typeof Yoga.UNIT_PERCENT).toBe("number");
|
||||
expect(typeof Yoga.UNIT_AUTO).toBe("number");
|
||||
|
||||
// Direction constants
|
||||
expect(typeof Yoga.DIRECTION_INHERIT).toBe("number");
|
||||
expect(typeof Yoga.DIRECTION_LTR).toBe("number");
|
||||
expect(typeof Yoga.DIRECTION_RTL).toBe("number");
|
||||
|
||||
// Display constants
|
||||
expect(typeof Yoga.DISPLAY_FLEX).toBe("number");
|
||||
expect(typeof Yoga.DISPLAY_NONE).toBe("number");
|
||||
|
||||
// Position type constants
|
||||
expect(typeof Yoga.POSITION_TYPE_STATIC).toBe("number");
|
||||
expect(typeof Yoga.POSITION_TYPE_RELATIVE).toBe("number");
|
||||
expect(typeof Yoga.POSITION_TYPE_ABSOLUTE).toBe("number");
|
||||
|
||||
// Overflow constants
|
||||
expect(typeof Yoga.OVERFLOW_VISIBLE).toBe("number");
|
||||
expect(typeof Yoga.OVERFLOW_HIDDEN).toBe("number");
|
||||
expect(typeof Yoga.OVERFLOW_SCROLL).toBe("number");
|
||||
|
||||
// Special value
|
||||
// Note: Yoga.UNDEFINED is not currently exposed in Bun's implementation
|
||||
// It would be YGUndefined (NaN) in the C++ code
|
||||
// expect(typeof Yoga.UNDEFINED).toBe("number");
|
||||
});
|
||||
});
|
||||
272
test/js/bun/yoga/yoga-node.test.ts
Normal file
272
test/js/bun/yoga/yoga-node.test.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
const Yoga = Bun.Yoga;
|
||||
|
||||
describe("Yoga.Node", () => {
|
||||
test("Node constructor", () => {
|
||||
const node = new Yoga.Node();
|
||||
expect(node).toBeDefined();
|
||||
expect(node.constructor.name).toBe("Node");
|
||||
});
|
||||
|
||||
test("Node.create() static method", () => {
|
||||
const node = Yoga.Node.create();
|
||||
expect(node).toBeDefined();
|
||||
expect(node.constructor.name).toBe("Node");
|
||||
});
|
||||
|
||||
test("Node with config", () => {
|
||||
const config = new Yoga.Config();
|
||||
const node = new Yoga.Node(config);
|
||||
expect(node).toBeDefined();
|
||||
});
|
||||
|
||||
test("setWidth with various values", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Number
|
||||
expect(() => node.setWidth(100)).not.toThrow();
|
||||
|
||||
// Percentage string
|
||||
expect(() => node.setWidth("50%")).not.toThrow();
|
||||
|
||||
// Auto
|
||||
expect(() => node.setWidth("auto")).not.toThrow();
|
||||
|
||||
// Object format
|
||||
expect(() => node.setWidth({ unit: Yoga.UNIT_POINT, value: 200 })).not.toThrow();
|
||||
expect(() => node.setWidth({ unit: Yoga.UNIT_PERCENT, value: 75 })).not.toThrow();
|
||||
|
||||
// Undefined/null
|
||||
expect(() => node.setWidth(undefined)).not.toThrow();
|
||||
expect(() => node.setWidth(null)).not.toThrow();
|
||||
});
|
||||
|
||||
test("getWidth returns correct format", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
node.setWidth(100);
|
||||
let width = node.getWidth();
|
||||
expect(width).toEqual({ unit: Yoga.UNIT_POINT, value: 100 });
|
||||
|
||||
node.setWidth("50%");
|
||||
width = node.getWidth();
|
||||
expect(width).toEqual({ unit: Yoga.UNIT_PERCENT, value: 50 });
|
||||
|
||||
node.setWidth("auto");
|
||||
width = node.getWidth();
|
||||
expect(width).toEqual({ unit: Yoga.UNIT_AUTO, value: expect.any(Number) });
|
||||
});
|
||||
|
||||
test("setMargin/getPadding edge values", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Set margins
|
||||
node.setMargin(Yoga.EDGE_TOP, 10);
|
||||
node.setMargin(Yoga.EDGE_RIGHT, "20%");
|
||||
node.setMargin(Yoga.EDGE_BOTTOM, "auto");
|
||||
node.setMargin(Yoga.EDGE_LEFT, { unit: Yoga.UNIT_POINT, value: 30 });
|
||||
|
||||
// Get margins
|
||||
expect(node.getMargin(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
|
||||
expect(node.getMargin(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 20 });
|
||||
expect(node.getMargin(Yoga.EDGE_BOTTOM)).toEqual({ unit: Yoga.UNIT_AUTO, value: expect.any(Number) });
|
||||
expect(node.getMargin(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 30 });
|
||||
});
|
||||
|
||||
test("flexbox properties", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Flex direction
|
||||
expect(() => node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW)).not.toThrow();
|
||||
expect(() => node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN)).not.toThrow();
|
||||
|
||||
// Justify content
|
||||
expect(() => node.setJustifyContent(Yoga.JUSTIFY_CENTER)).not.toThrow();
|
||||
expect(() => node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN)).not.toThrow();
|
||||
|
||||
// Align items
|
||||
expect(() => node.setAlignItems(Yoga.ALIGN_CENTER)).not.toThrow();
|
||||
expect(() => node.setAlignItems(Yoga.ALIGN_FLEX_START)).not.toThrow();
|
||||
|
||||
// Flex properties
|
||||
expect(() => node.setFlex(1)).not.toThrow();
|
||||
expect(() => node.setFlexGrow(2)).not.toThrow();
|
||||
expect(() => node.setFlexShrink(0.5)).not.toThrow();
|
||||
expect(() => node.setFlexBasis(100)).not.toThrow();
|
||||
expect(() => node.setFlexBasis("auto")).not.toThrow();
|
||||
});
|
||||
|
||||
test("hierarchy operations", () => {
|
||||
const parent = new Yoga.Node();
|
||||
const child1 = new Yoga.Node();
|
||||
const child2 = new Yoga.Node();
|
||||
|
||||
// Insert children
|
||||
parent.insertChild(child1, 0);
|
||||
parent.insertChild(child2, 1);
|
||||
|
||||
expect(parent.getChildCount()).toBe(2);
|
||||
expect(parent.getChild(0)).toBe(child1);
|
||||
expect(parent.getChild(1)).toBe(child2);
|
||||
|
||||
expect(child1.getParent()).toBe(parent);
|
||||
expect(child2.getParent()).toBe(parent);
|
||||
|
||||
// Remove child
|
||||
parent.removeChild(child1);
|
||||
expect(parent.getChildCount()).toBe(1);
|
||||
expect(parent.getChild(0)).toBe(child2);
|
||||
expect(child1.getParent()).toBeNull();
|
||||
});
|
||||
|
||||
test("layout calculation", () => {
|
||||
const root = new Yoga.Node();
|
||||
root.setWidth(500);
|
||||
root.setHeight(300);
|
||||
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
|
||||
const child = new Yoga.Node();
|
||||
child.setFlex(1);
|
||||
root.insertChild(child, 0);
|
||||
|
||||
// Calculate layout
|
||||
root.calculateLayout(500, 300, Yoga.DIRECTION_LTR);
|
||||
|
||||
// Get computed layout
|
||||
const layout = root.getComputedLayout();
|
||||
expect(layout).toHaveProperty("left");
|
||||
expect(layout).toHaveProperty("top");
|
||||
expect(layout).toHaveProperty("width");
|
||||
expect(layout).toHaveProperty("height");
|
||||
expect(layout.width).toBe(500);
|
||||
expect(layout.height).toBe(300);
|
||||
|
||||
const childLayout = child.getComputedLayout();
|
||||
expect(childLayout.width).toBe(500); // Should fill parent width
|
||||
expect(childLayout.height).toBe(300); // Should fill parent height
|
||||
});
|
||||
|
||||
test("measure function", () => {
|
||||
const node = new Yoga.Node();
|
||||
let measureCalled = false;
|
||||
|
||||
const measureFunc = (width, widthMode, height, heightMode) => {
|
||||
measureCalled = true;
|
||||
return { width: 100, height: 50 };
|
||||
};
|
||||
|
||||
node.setMeasureFunc(measureFunc);
|
||||
node.markDirty();
|
||||
|
||||
// Calculate layout - this should call measure function
|
||||
node.calculateLayout();
|
||||
expect(measureCalled).toBe(true);
|
||||
|
||||
// Clear measure function
|
||||
node.setMeasureFunc(null);
|
||||
});
|
||||
|
||||
test("dirtied callback", () => {
|
||||
const node = new Yoga.Node();
|
||||
let dirtiedCalled = false;
|
||||
|
||||
const dirtiedFunc = () => {
|
||||
dirtiedCalled = true;
|
||||
};
|
||||
|
||||
node.setDirtiedFunc(dirtiedFunc);
|
||||
|
||||
// markDirty requires a measure function
|
||||
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
|
||||
|
||||
// Nodes start dirty, so clear the dirty flag first
|
||||
node.calculateLayout();
|
||||
expect(node.isDirty()).toBe(false);
|
||||
|
||||
// Now mark dirty - this should trigger the callback
|
||||
node.markDirty();
|
||||
|
||||
expect(dirtiedCalled).toBe(true);
|
||||
|
||||
// Clear dirtied function
|
||||
node.setDirtiedFunc(null);
|
||||
});
|
||||
|
||||
test("reset node", () => {
|
||||
const node = new Yoga.Node();
|
||||
node.setWidth(100);
|
||||
node.setHeight(200);
|
||||
node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
|
||||
|
||||
node.reset();
|
||||
|
||||
// After reset, width/height default to AUTO, not UNDEFINED
|
||||
const width = node.getWidth();
|
||||
expect(width.unit).toBe(Yoga.UNIT_AUTO);
|
||||
});
|
||||
|
||||
test("dirty state", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Nodes start as dirty by default in Yoga
|
||||
expect(node.isDirty()).toBe(true);
|
||||
|
||||
// Calculate layout clears dirty flag
|
||||
node.calculateLayout();
|
||||
expect(node.isDirty()).toBe(false);
|
||||
|
||||
// Mark as dirty (requires measure function)
|
||||
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
|
||||
node.markDirty();
|
||||
expect(node.isDirty()).toBe(true);
|
||||
|
||||
// Calculate layout clears dirty flag again
|
||||
node.calculateLayout();
|
||||
expect(node.isDirty()).toBe(false);
|
||||
});
|
||||
|
||||
test("free node", () => {
|
||||
const node = new Yoga.Node();
|
||||
expect(() => node.free()).not.toThrow();
|
||||
// After free, the node should not crash but operations may not work
|
||||
});
|
||||
|
||||
test("aspect ratio", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
// Set aspect ratio
|
||||
expect(() => node.setAspectRatio(16 / 9)).not.toThrow();
|
||||
expect(() => node.setAspectRatio(undefined)).not.toThrow();
|
||||
expect(() => node.setAspectRatio(null)).not.toThrow();
|
||||
});
|
||||
|
||||
test("display type", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(() => node.setDisplay(Yoga.DISPLAY_FLEX)).not.toThrow();
|
||||
expect(() => node.setDisplay(Yoga.DISPLAY_NONE)).not.toThrow();
|
||||
});
|
||||
|
||||
test("overflow", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(() => node.setOverflow(Yoga.OVERFLOW_VISIBLE)).not.toThrow();
|
||||
expect(() => node.setOverflow(Yoga.OVERFLOW_HIDDEN)).not.toThrow();
|
||||
expect(() => node.setOverflow(Yoga.OVERFLOW_SCROLL)).not.toThrow();
|
||||
});
|
||||
|
||||
test("position type", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(() => node.setPositionType(Yoga.POSITION_TYPE_RELATIVE)).not.toThrow();
|
||||
expect(() => node.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE)).not.toThrow();
|
||||
});
|
||||
|
||||
test("gap property", () => {
|
||||
const node = new Yoga.Node();
|
||||
|
||||
expect(() => node.setGap(Yoga.GUTTER_ROW, 10)).not.toThrow();
|
||||
expect(() => node.setGap(Yoga.GUTTER_COLUMN, 20)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -887,6 +887,68 @@ for (let withOverridenBufferWrite of [false, true]) {
|
||||
expect(f[1]).toBe(0x6f);
|
||||
});
|
||||
|
||||
it("slice() with fractional offsets truncates toward zero", () => {
|
||||
const buf = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
|
||||
// -0.1 should truncate to 0, not -1
|
||||
const a = buf.slice(-0.1);
|
||||
expect(a.length).toBe(10);
|
||||
expect(a[0]).toBe(0);
|
||||
|
||||
// -1.9 should truncate to -1, not -2
|
||||
const b = buf.slice(-1.9);
|
||||
expect(b.length).toBe(1);
|
||||
expect(b[0]).toBe(9);
|
||||
|
||||
// 1.9 should truncate to 1
|
||||
const c = buf.slice(1.9, 4.1);
|
||||
expect(c.length).toBe(3);
|
||||
expect(c[0]).toBe(1);
|
||||
expect(c[1]).toBe(2);
|
||||
expect(c[2]).toBe(3);
|
||||
|
||||
// NaN should be treated as 0
|
||||
const d = buf.slice(NaN, NaN);
|
||||
expect(d.length).toBe(0);
|
||||
|
||||
const e = buf.slice(NaN);
|
||||
expect(e.length).toBe(10);
|
||||
});
|
||||
|
||||
it("slice() on detached buffer throws TypeError", () => {
|
||||
const ab = new ArrayBuffer(10);
|
||||
const buf = Buffer.from(ab);
|
||||
// Detach the ArrayBuffer by transferring it
|
||||
structuredClone(ab, { transfer: [ab] });
|
||||
expect(() => buf.slice(0, 5)).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("subarray() on detached buffer throws TypeError", () => {
|
||||
const ab = new ArrayBuffer(10);
|
||||
const buf = Buffer.from(ab);
|
||||
structuredClone(ab, { transfer: [ab] });
|
||||
expect(() => buf.subarray(0, 5)).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("slice() on resizable ArrayBuffer returns fixed-length view", () => {
|
||||
const rab = new ArrayBuffer(10, { maxByteLength: 20 });
|
||||
const buf = Buffer.from(rab);
|
||||
buf[0] = 1;
|
||||
buf[1] = 2;
|
||||
buf[2] = 3;
|
||||
buf[3] = 4;
|
||||
buf[4] = 5;
|
||||
|
||||
const sliced = buf.slice(0, 5);
|
||||
expect(sliced.length).toBe(5);
|
||||
expect(sliced[0]).toBe(1);
|
||||
expect(sliced[4]).toBe(5);
|
||||
|
||||
// Growing the buffer should NOT change the slice length
|
||||
rab.resize(20);
|
||||
expect(sliced.length).toBe(5);
|
||||
});
|
||||
|
||||
function forEachUnicode(label, test) {
|
||||
["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding =>
|
||||
it(`${label} (${encoding})`, test.bind(null, encoding)),
|
||||
|
||||
@@ -90,6 +90,273 @@ describe("Structured Clone Fast Path", () => {
|
||||
expect(delta).toBeLessThan(1024 * 1024);
|
||||
});
|
||||
|
||||
// === Array fast path tests ===
|
||||
|
||||
test("structuredClone should work with empty array", () => {
|
||||
expect(structuredClone([])).toEqual([]);
|
||||
});
|
||||
|
||||
test("structuredClone should work with array of numbers", () => {
|
||||
const input = [1, 2, 3, 4, 5];
|
||||
expect(structuredClone(input)).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone should work with array of strings", () => {
|
||||
const input = ["hello", "world", ""];
|
||||
expect(structuredClone(input)).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone should work with array of mixed primitives", () => {
|
||||
const input = [1, "hello", true, false, null, undefined, 3.14];
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone should work with array of special numbers", () => {
|
||||
const cloned = structuredClone([-0, NaN, Infinity, -Infinity]);
|
||||
expect(Object.is(cloned[0], -0)).toBe(true);
|
||||
expect(cloned[1]).toBeNaN();
|
||||
expect(cloned[2]).toBe(Infinity);
|
||||
expect(cloned[3]).toBe(-Infinity);
|
||||
});
|
||||
|
||||
test("structuredClone should work with large array of numbers", () => {
|
||||
const input = Array.from({ length: 10000 }, (_, i) => i);
|
||||
expect(structuredClone(input)).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone should fallback for arrays with nested objects", () => {
|
||||
const input = [{ a: 1 }, { b: 2 }];
|
||||
expect(structuredClone(input)).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone should fallback for arrays with holes", () => {
|
||||
const input = [1, , 3]; // sparse
|
||||
const cloned = structuredClone(input);
|
||||
// structured clone spec: holes become undefined
|
||||
expect(cloned[0]).toBe(1);
|
||||
expect(cloned[1]).toBe(undefined);
|
||||
expect(cloned[2]).toBe(3);
|
||||
});
|
||||
|
||||
test("structuredClone should work with array of doubles", () => {
|
||||
const input = [1.5, 2.7, 3.14, 0.1 + 0.2];
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual(input);
|
||||
});
|
||||
|
||||
test("structuredClone creates independent copy of array", () => {
|
||||
const input = [1, 2, 3];
|
||||
const cloned = structuredClone(input);
|
||||
cloned[0] = 999;
|
||||
expect(input[0]).toBe(1);
|
||||
});
|
||||
|
||||
test("structuredClone should preserve named properties on arrays", () => {
|
||||
const input: any = [1, 2, 3];
|
||||
input.foo = "bar";
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned.foo).toBe("bar");
|
||||
expect(Array.from(cloned)).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("postMessage should work with array fast path", async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
const input = [1, 2, 3, "hello", true];
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
port2.onmessage = (e: MessageEvent) => resolve(e.data);
|
||||
port1.postMessage(input);
|
||||
const result = await promise;
|
||||
expect(result).toEqual(input);
|
||||
port1.close();
|
||||
port2.close();
|
||||
});
|
||||
|
||||
// === Edge case tests ===
|
||||
|
||||
test("structuredClone of frozen array should produce a non-frozen clone", () => {
|
||||
const input = Object.freeze([1, 2, 3]);
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual([1, 2, 3]);
|
||||
expect(Object.isFrozen(cloned)).toBe(false);
|
||||
cloned[0] = 999;
|
||||
expect(cloned[0]).toBe(999);
|
||||
});
|
||||
|
||||
test("structuredClone of sealed array should produce a non-sealed clone", () => {
|
||||
const input = Object.seal([1, 2, 3]);
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual([1, 2, 3]);
|
||||
expect(Object.isSealed(cloned)).toBe(false);
|
||||
cloned.push(4);
|
||||
expect(cloned).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test("structuredClone of array with deleted element (hole via delete)", () => {
|
||||
const input = [1, 2, 3];
|
||||
delete (input as any)[1];
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned[0]).toBe(1);
|
||||
expect(cloned[1]).toBe(undefined);
|
||||
expect(cloned[2]).toBe(3);
|
||||
expect(1 in cloned).toBe(false); // holes remain holes after structuredClone
|
||||
});
|
||||
|
||||
test("structuredClone of array with length > actual elements", () => {
|
||||
const input = [1, 2, 3];
|
||||
input.length = 6;
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned.length).toBe(6);
|
||||
expect(cloned[0]).toBe(1);
|
||||
expect(cloned[1]).toBe(2);
|
||||
expect(cloned[2]).toBe(3);
|
||||
expect(cloned[3]).toBe(undefined);
|
||||
});
|
||||
|
||||
test("structuredClone of single element arrays", () => {
|
||||
expect(structuredClone([42])).toEqual([42]);
|
||||
expect(structuredClone([3.14])).toEqual([3.14]);
|
||||
expect(structuredClone(["hello"])).toEqual(["hello"]);
|
||||
expect(structuredClone([true])).toEqual([true]);
|
||||
expect(structuredClone([null])).toEqual([null]);
|
||||
});
|
||||
|
||||
test("structuredClone of array with named properties on Int32 array", () => {
|
||||
const input: any = [1, 2, 3]; // Int32 indexing
|
||||
input.name = "test";
|
||||
input.count = 42;
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned.name).toBe("test");
|
||||
expect(cloned.count).toBe(42);
|
||||
expect(Array.from(cloned)).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("structuredClone of array with named properties on Double array", () => {
|
||||
const input: any = [1.1, 2.2, 3.3]; // Double indexing
|
||||
input.label = "doubles";
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned.label).toBe("doubles");
|
||||
expect(Array.from(cloned)).toEqual([1.1, 2.2, 3.3]);
|
||||
});
|
||||
|
||||
test("structuredClone of array that transitions Int32 to Double", () => {
|
||||
const input = [1, 2, 3]; // starts as Int32
|
||||
input.push(4.5); // transitions to Double
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual([1, 2, 3, 4.5]);
|
||||
});
|
||||
|
||||
test("structuredClone of array with modified prototype", () => {
|
||||
const input = [1, 2, 3];
|
||||
Object.setPrototypeOf(input, {
|
||||
customMethod() {
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
const cloned = structuredClone(input);
|
||||
// Clone should have standard Array prototype, not the custom one
|
||||
expect(Array.from(cloned)).toEqual([1, 2, 3]);
|
||||
expect(cloned).toBeInstanceOf(Array);
|
||||
expect((cloned as any).customMethod).toBeUndefined();
|
||||
});
|
||||
|
||||
test("structuredClone of array with prototype indexed properties and holes", () => {
|
||||
const proto = Object.create(Array.prototype);
|
||||
proto[1] = "from proto";
|
||||
const input = new Array(3);
|
||||
Object.setPrototypeOf(input, proto);
|
||||
input[0] = "a";
|
||||
input[2] = "c";
|
||||
// structuredClone only copies own properties; prototype values are not included
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned[0]).toBe("a");
|
||||
expect(1 in cloned).toBe(false); // hole, not "from proto"
|
||||
expect(cloned[2]).toBe("c");
|
||||
expect(cloned).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
test("postMessage with Int32 array via MessageChannel", async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
const input = [10, 20, 30, 40, 50];
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
port2.onmessage = (e: MessageEvent) => resolve(e.data);
|
||||
port1.postMessage(input);
|
||||
const result = await promise;
|
||||
expect(result).toEqual(input);
|
||||
port1.close();
|
||||
port2.close();
|
||||
});
|
||||
|
||||
test("postMessage with Double array via MessageChannel", async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
const input = [1.1, 2.2, 3.3];
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
port2.onmessage = (e: MessageEvent) => resolve(e.data);
|
||||
port1.postMessage(input);
|
||||
const result = await promise;
|
||||
expect(result).toEqual(input);
|
||||
port1.close();
|
||||
port2.close();
|
||||
});
|
||||
|
||||
test("structuredClone of array multiple times produces independent copies", () => {
|
||||
const input = [1, 2, 3];
|
||||
const clones = Array.from({ length: 10 }, () => structuredClone(input));
|
||||
clones[0][0] = 999;
|
||||
clones[5][1] = 888;
|
||||
// All other clones and the original should be unaffected
|
||||
expect(input).toEqual([1, 2, 3]);
|
||||
for (let i = 1; i < 10; i++) {
|
||||
if (i === 5) {
|
||||
expect(clones[i]).toEqual([1, 888, 3]);
|
||||
} else {
|
||||
expect(clones[i]).toEqual([1, 2, 3]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("structuredClone of Array subclass loses subclass identity", () => {
|
||||
class MyArray extends Array {
|
||||
customProp = "hello";
|
||||
sum() {
|
||||
return this.reduce((a: number, b: number) => a + b, 0);
|
||||
}
|
||||
}
|
||||
const input = new MyArray(1, 2, 3);
|
||||
input.customProp = "world";
|
||||
const cloned = structuredClone(input);
|
||||
// structuredClone spec: result is a plain Array, not a subclass
|
||||
expect(Array.from(cloned)).toEqual([1, 2, 3]);
|
||||
expect(cloned).toBeInstanceOf(Array);
|
||||
expect((cloned as any).sum).toBeUndefined();
|
||||
});
|
||||
|
||||
test("structuredClone of array with only undefined values", () => {
|
||||
const input = [undefined, undefined, undefined];
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual([undefined, undefined, undefined]);
|
||||
expect(cloned.length).toBe(3);
|
||||
// Ensure they are actual values, not holes
|
||||
expect(0 in cloned).toBe(true);
|
||||
expect(1 in cloned).toBe(true);
|
||||
expect(2 in cloned).toBe(true);
|
||||
});
|
||||
|
||||
test("structuredClone of array with only null values", () => {
|
||||
const input = [null, null, null];
|
||||
const cloned = structuredClone(input);
|
||||
expect(cloned).toEqual([null, null, null]);
|
||||
});
|
||||
|
||||
test("structuredClone of dense double array preserves -0 and NaN", () => {
|
||||
const input = [-0, NaN, -0, NaN];
|
||||
const cloned = structuredClone(input);
|
||||
expect(Object.is(cloned[0], -0)).toBe(true);
|
||||
expect(cloned[1]).toBeNaN();
|
||||
expect(Object.is(cloned[2], -0)).toBe(true);
|
||||
expect(cloned[3]).toBeNaN();
|
||||
});
|
||||
|
||||
test("structuredClone on object with simple properties can exceed JSFinalObject::maxInlineCapacity", () => {
|
||||
let largeValue = {};
|
||||
for (let i = 0; i < 100; i++) {
|
||||
|
||||
Reference in New Issue
Block a user