From 370402263065e51ad18a5f4429f8066b1a2dbaa4 Mon Sep 17 00:00:00 2001 From: Simon Corsin Date: Sun, 21 Jun 2026 14:58:43 -0500 Subject: [PATCH 01/18] compilation fix --- .bazelrc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.bazelrc b/.bazelrc index eccc0599..29483821 100644 --- a/.bazelrc +++ b/.bazelrc @@ -105,3 +105,10 @@ build --repo_env=CARGO_BAZEL_REPIN=true # Remote cache (activated in CI via .bazelrc.local) build:ci --experimental_circuit_breaker_strategy=failure + +build --cxxopt=-Wno-deprecated-builtins +build --cxxopt=-Wno-deprecated-literal-operator +build --host_cxxopt=-Wno-deprecated-builtins +build --host_cxxopt=-Wno-deprecated-literal-operator +build --cxxopt=-Wno-nontrivial-memcall +build --host_cxxopt=-Wno-nontrivial-memcall \ No newline at end of file From 663d6e5fa9ebe34f29ee7e28f8b66decb5413206 Mon Sep 17 00:00:00 2001 From: Simon Corsin Date: Sun, 21 Jun 2026 15:17:30 -0500 Subject: [PATCH 02/18] Fix baseline module tests --- .../file_system/src/FileSystemModule.d.ts | 8 ++- .../valdi/file_system/test/FileSystem.spec.ts | 71 ++++++++++++++++--- .../src/valdi/file_system/web/FileSystem.ts | 21 ++++-- .../valdi/foundation/src/DeferredPromise.ts | 1 + .../src/valdi/valdi_protobuf/BUILD.bazel | 3 +- .../valdi_protobuf/web/test/run_tests.js | 26 +++++-- .../valdi/valdi_test/test/JSXTest.spec.tsx | 9 ++- .../JavaScript/Modules/FileSystemFactory.cpp | 16 +++++ 8 files changed, 126 insertions(+), 29 deletions(-) diff --git a/src/valdi_modules/src/valdi/file_system/src/FileSystemModule.d.ts b/src/valdi_modules/src/valdi/file_system/src/FileSystemModule.d.ts index 158a8984..62d1d6cf 100644 --- a/src/valdi_modules/src/valdi/file_system/src/FileSystemModule.d.ts +++ b/src/valdi_modules/src/valdi/file_system/src/FileSystemModule.d.ts @@ -4,6 +4,8 @@ export interface ReadFileOptions { encoding?: FileEncoding | undefined | null; } +export type WriteFileData = ArrayBuffer | Uint8Array | string; + /** * Valdi File System module * This FS API right now is only for internal usage due to some limitations. @@ -11,13 +13,15 @@ export interface ReadFileOptions { * If you have any questions about the usage this module please ask in the support channel */ export interface FileSystemModule { + existsSync(path: string): boolean; + removeSync(path: string): boolean; createDirectorySync(path: string, createIntermediates: boolean): boolean; readFileSync(path: string, options?: ReadFileOptions): string | ArrayBuffer; - writeFileSync(path: string, data: ArrayBuffer | string): void; + writeFileSync(path: string, data: WriteFileData): void; currentWorkingDirectory(): string; -} \ No newline at end of file +} diff --git a/src/valdi_modules/src/valdi/file_system/test/FileSystem.spec.ts b/src/valdi_modules/src/valdi/file_system/test/FileSystem.spec.ts index b13e76b5..5c817c97 100644 --- a/src/valdi_modules/src/valdi/file_system/test/FileSystem.spec.ts +++ b/src/valdi_modules/src/valdi/file_system/test/FileSystem.spec.ts @@ -3,29 +3,58 @@ import { arrayToString } from 'coreutils/src/Uint8ArrayUtils'; import { fs, VALDI_MODULES_ROOT } from '../src/FileSystem'; describe('File System Module', () => { + const testFolder = `${VALDI_MODULES_ROOT}/file_system/test`; const newFSItems = { - file: `${VALDI_MODULES_ROOT}/file_system/test/new_test_file.txt`, - folder: `${VALDI_MODULES_ROOT}/file_system/test/new_folder`, + file: `${testFolder}/new_test_file.txt`, + folder: `${testFolder}/new_folder`, }; + function removeIfExists(path: string): void { + if (fs.existsSync(path)) { + fs.removeSync(path); + } + } + + beforeEach(() => { + removeIfExists(newFSItems.file); + removeIfExists(newFSItems.folder); + }); + + afterEach(() => { + removeIfExists(newFSItems.file); + removeIfExists(newFSItems.folder); + }); + it('should read file with content inside', () => { - const fileName = `${VALDI_MODULES_ROOT}/file_system/test/test_file.txt`; + const fileName = `${VALDI_MODULES_ROOT}/file_system/test/read_test_file.txt`; const fileContent = 'test data for file'; - const result = fs.readFileSync(fileName, { encoding: 'utf8' }); - expect(result).toEqual(fileContent); + fs.writeFileSync(fileName, fileContent); + try { + const result = fs.readFileSync(fileName, { encoding: 'utf8' }); + + expect(result).toEqual(fileContent); + } finally { + fs.removeSync(fileName); + } }); it('should read file as array buffer', () => { - const fileName = `${VALDI_MODULES_ROOT}/file_system/test/test_file.txt`; + const fileName = `${VALDI_MODULES_ROOT}/file_system/test/read_array_buffer_test_file.txt`; const fileContent = 'test data for file'; - const result = fs.readFileSync(fileName); - expect(result).toBeInstanceOf(ArrayBuffer); + fs.writeFileSync(fileName, fileContent); + try { + const result = fs.readFileSync(fileName); - const stringContent = arrayToString(new Uint8Array(result as ArrayBuffer)); + expect(result).toBeInstanceOf(ArrayBuffer); - expect(stringContent).toEqual(fileContent); + const stringContent = arrayToString(new Uint8Array(result as ArrayBuffer)); + + expect(stringContent).toEqual(fileContent); + } finally { + fs.removeSync(fileName); + } }); it('should throw an error for read file operation if file does not exist', () => { @@ -38,6 +67,8 @@ describe('File System Module', () => { const fileContent = 'test data for file'; fs.writeFileSync(newFSItems.file, fileContent); + expect(fs.existsSync(newFSItems.file)).toBeTrue(); + const result = fs.readFileSync(newFSItems.file, { encoding: 'utf8' }); expect(result).toEqual(fileContent); @@ -45,6 +76,22 @@ describe('File System Module', () => { const resultFromFileRemove = fs.removeSync(newFSItems.file); expect(resultFromFileRemove).toBeTrue(); + expect(fs.existsSync(newFSItems.file)).toBeFalse(); + }); + + it('should write Uint8Array content', () => { + const fileName = `${VALDI_MODULES_ROOT}/file_system/test/write_uint8_array_test_file.txt`; + const source = new Uint8Array([120, 116, 101, 115, 116, 32, 100, 97, 116, 97, 121]); + const fileContent = source.subarray(1, source.length - 1); + + fs.writeFileSync(fileName, fileContent); + try { + const result = fs.readFileSync(fileName, { encoding: 'utf8' }); + + expect(result).toEqual('test data'); + } finally { + fs.removeSync(fileName); + } }); it('should get current root directory for client repository', () => { @@ -52,9 +99,13 @@ describe('File System Module', () => { }); it('should create and remove a new folder', () => { + fs.createDirectorySync(testFolder, true); + const result = fs.createDirectorySync(newFSItems.folder, false); expect(result).toBeTrue(); + expect(fs.createDirectorySync(newFSItems.folder, true)).toBeTrue(); + expect(fs.existsSync(newFSItems.folder)).toBeTrue(); const resultFromFolderRemove = fs.removeSync(newFSItems.folder); diff --git a/src/valdi_modules/src/valdi/file_system/web/FileSystem.ts b/src/valdi_modules/src/valdi/file_system/web/FileSystem.ts index bb3951b4..5befbf1f 100644 --- a/src/valdi_modules/src/valdi/file_system/web/FileSystem.ts +++ b/src/valdi_modules/src/valdi/file_system/web/FileSystem.ts @@ -1,4 +1,4 @@ -import { FileSystemModule, ReadFileOptions } from '../src/FileSystemModule'; +import { FileSystemModule, ReadFileOptions, WriteFileData } from '../src/FileSystemModule'; const encUtf8 = new TextEncoder(); const decUtf8 = new TextDecoder(); @@ -46,9 +46,20 @@ const dirs = new Set([ROOT]); const files = new Map(); let CWD = ROOT; +function toBytes(data: WriteFileData): Uint8Array { + if (typeof data === "string") return encUtf8.encode(data); + if (data instanceof Uint8Array) return data.slice(); + return new Uint8Array(data).slice(); +} + /* -------- implementation -------- */ const fsStub: FileSystemModule = { + existsSync(path: string): boolean { + const p = norm(path); + return files.has(p) || dirs.has(p); + }, + removeSync(path: string): boolean { const p = norm(path); if (files.delete(p)) return true; @@ -106,16 +117,14 @@ const fsStub: FileSystemModule = { return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); }, - writeFileSync(path: string, data: ArrayBuffer | string): void { + writeFileSync(path: string, data: WriteFileData): void { const p = norm(path); // ensure parent dir exists (be friendly in the stub) const parent = parentDir(p); if (!dirs.has(parent)) { this.createDirectorySync(parent, true); } - const bytes = - typeof data === "string" ? encUtf8.encode(data) : new Uint8Array(data); - files.set(p, bytes); + files.set(p, toBytes(data)); }, currentWorkingDirectory(): string { @@ -127,4 +136,4 @@ const fsStub: FileSystemModule = { export let fileSystem: FileSystemModule = fsStub; export function setFileSystem(impl: FileSystemModule): void { fileSystem = impl; -} \ No newline at end of file +} diff --git a/src/valdi_modules/src/valdi/foundation/src/DeferredPromise.ts b/src/valdi_modules/src/valdi/foundation/src/DeferredPromise.ts index 7ca452ca..de31fb5a 100644 --- a/src/valdi_modules/src/valdi/foundation/src/DeferredPromise.ts +++ b/src/valdi_modules/src/valdi/foundation/src/DeferredPromise.ts @@ -4,6 +4,7 @@ export class DeferredPromiseTimeoutError extends Error { constructor(message: string) { super(message); + this.message = message; this.name = 'DeferredPromiseTimeoutError'; Object.setPrototypeOf(this, DeferredPromiseTimeoutError.prototype); } diff --git a/src/valdi_modules/src/valdi/valdi_protobuf/BUILD.bazel b/src/valdi_modules/src/valdi/valdi_protobuf/BUILD.bazel index 70d7ad8d..11024880 100644 --- a/src/valdi_modules/src/valdi/valdi_protobuf/BUILD.bazel +++ b/src/valdi_modules/src/valdi/valdi_protobuf/BUILD.bazel @@ -112,7 +112,8 @@ npm_link_all_packages(name = "node_modules") js_test( name = "valdi_protobuf_web_test", data = [ - ":node_modules/jest", + "//bzl/valdi/npm:node_modules/@protobuf-ts/runtime", + "//bzl/valdi/npm:node_modules/jest", ":proto_files", ":valdi_protobuf_web_compiled", ], diff --git a/src/valdi_modules/src/valdi/valdi_protobuf/web/test/run_tests.js b/src/valdi_modules/src/valdi/valdi_protobuf/web/test/run_tests.js index 4bb9fcae..24e25bb5 100644 --- a/src/valdi_modules/src/valdi/valdi_protobuf/web/test/run_tests.js +++ b/src/valdi_modules/src/valdi/valdi_protobuf/web/test/run_tests.js @@ -19,6 +19,10 @@ if (!runfiles) { const possiblePaths = [ path.join(runfiles, 'valdi/src/valdi_modules/src/valdi/valdi_protobuf/node_modules'), // Local valdi workspace path.join(runfiles, '+local_repos+valdi/src/valdi_modules/src/valdi/valdi_protobuf/node_modules'), // External workspace referencing valdi + path.join(runfiles, '_main/src/valdi_modules/src/valdi/valdi_protobuf/node_modules'), // Bzlmod main workspace + path.join(runfiles, 'valdi/bzl/valdi/npm/node_modules'), // Shared npm repository + path.join(runfiles, '+local_repos+valdi/bzl/valdi/npm/node_modules'), // Shared npm repository through local_repos + path.join(runfiles, '_main/bzl/valdi/npm/node_modules'), // Shared npm repository in the main workspace ]; let nodeModulesPath = null; @@ -45,12 +49,24 @@ if (!fs.existsSync(jestBin)) { process.exit(1); } -// The compiled test files are in the dist output directory -// Only test files from valdi_protobuf/web/test (out_dir is "web" so compiled files are at web/test) -const testDir = path.join(runfiles, 'valdi/src/valdi_modules/src/valdi/valdi_protobuf/web/test'); +// The compiled test files are in the dist output directory. +// Only test files from valdi_protobuf/web/test (out_dir is "web" so compiled files are at web/test). +const possibleTestDirs = [ + path.join(runfiles, 'valdi/src/valdi_modules/src/valdi/valdi_protobuf/web/test'), + path.join(runfiles, '+local_repos+valdi/src/valdi_modules/src/valdi/valdi_protobuf/web/test'), + path.join(runfiles, '_main/src/valdi_modules/src/valdi/valdi_protobuf/web/test'), +]; + +let testDir = null; +for (const p of possibleTestDirs) { + if (fs.existsSync(p)) { + testDir = p; + break; + } +} -if (!fs.existsSync(testDir)) { - console.error(`Test directory not found: ${testDir}`); +if (!testDir) { + console.error(`Test directory not found in any of: ${possibleTestDirs.join(', ')}`); process.exit(1); } diff --git a/src/valdi_modules/src/valdi/valdi_test/test/JSXTest.spec.tsx b/src/valdi_modules/src/valdi/valdi_test/test/JSXTest.spec.tsx index 925be50d..a442b893 100644 --- a/src/valdi_modules/src/valdi/valdi_test/test/JSXTest.spec.tsx +++ b/src/valdi_modules/src/valdi/valdi_test/test/JSXTest.spec.tsx @@ -54,12 +54,11 @@ describe('JSX', () => { } const component = createComponent(WebViewComponent); - const rootNode = await component.getRenderedNode(); - const simplified = rootNode.simplify(['viewClass', 'attributes']); - const attributes = simplified.attributes!; + const componentNode = component.getComponent().renderer.getComponentVirtualNode(component.getComponent()); + const webViewElement = componentNode.children[0].element!; - expect(simplified.viewClass).toEqual('SCValdiWebView'); - expect(attributes.controller).toBeDefined(); + expect(webViewElement.viewClass).toEqual('SCValdiWebView'); + expect(webViewElement.getAttribute('controller')).toBeDefined(); }); it('can render a component with view model', async () => { diff --git a/valdi/src/valdi/runtime/JavaScript/Modules/FileSystemFactory.cpp b/valdi/src/valdi/runtime/JavaScript/Modules/FileSystemFactory.cpp index d5f707b0..1e4ba4e6 100644 --- a/valdi/src/valdi/runtime/JavaScript/Modules/FileSystemFactory.cpp +++ b/valdi/src/valdi/runtime/JavaScript/Modules/FileSystemFactory.cpp @@ -20,6 +20,19 @@ Value FileSystemFactory::loadModule() { auto strongThis = shared_from_this(); + out.setMapValue( + "existsSync", + Value(makeShared([strongThis](const ValueFunctionCallContext& callContext) -> Value { + auto pathNameResult = callContext.getParameterAsString(0); + + if (!callContext.getExceptionTracker()) { + return Value::undefined(); + } + + Path const path = Path(pathNameResult.toStringView()); + return Value(DiskUtils::stat(path).exists()); + }))); + out.setMapValue( "removeSync", Value(makeShared([strongThis](const ValueFunctionCallContext& callContext) -> Value { @@ -57,6 +70,9 @@ Value FileSystemFactory::loadModule() { } Path const path = Path(pathName.toStringView()); + if (DiskUtils::isDirectory(path)) { + return Value(true); + } bool const result = DiskUtils::makeDirectory(path, createIntermediates); if (!result) { From 4bf13aaef8ff59aad02a1be98fcd4d54c9fd505b Mon Sep 17 00:00:00 2001 From: Simon Corsin Date: Sun, 21 Jun 2026 15:30:38 -0500 Subject: [PATCH 03/18] Backport core text styling and layout support --- .../valdi/hello_world/src/HelloWorldApp.tsx | 70 +- docs/api/api-quick-reference.md | 6 +- docs/api/api-reference-elements.md | 12 +- docs/api/api-style-attributes.md | 6 +- snap_drawing/src/benchmark/main.cpp | 20 +- .../src/snap_drawing/cpp/Drawing/Paint.cpp | 17 + .../src/snap_drawing/cpp/Drawing/Paint.hpp | 2 + .../src/snap_drawing/cpp/Layers/TextLayer.cpp | 144 +++- .../src/snap_drawing/cpp/Layers/TextLayer.hpp | 26 +- .../snap_drawing/cpp/Text/AttributedText.hpp | 3 + .../src/snap_drawing/cpp/Text/FontManager.cpp | 26 + .../src/snap_drawing/cpp/Text/TextLayout.cpp | 39 +- .../src/snap_drawing/cpp/Text/TextLayout.hpp | 100 ++- .../cpp/Text/TextLayoutBuilder.cpp | 208 ++++-- .../cpp/Text/TextLayoutBuilder.hpp | 78 ++- snap_drawing/test/src/TextLayout_Tests.cpp | 663 ++++++++++++------ snap_drawing/test/utils/TestDataUtils.cpp | 33 +- snap_drawing/test/utils/TestFontUtils.cpp | 26 + snap_drawing/test/utils/TestFontUtils.hpp | 18 + snap_drawing/testdata/NotoSans-LICENSE.txt | 94 +++ snap_drawing/testdata/NotoSans-Regular.ttf | Bin 0 -> 569208 bytes .../src/valdi/valdi_core/src/SystemFont.ts | 66 ++ .../src/utils/AttributedTextBuilder.ts | 53 +- .../test/AttributedTextBuilder.spec.ts | 161 ++++- .../valdi/valdi_tsx/src/AttributedText.d.ts | 25 + .../valdi_tsx/src/NativeTemplateElements.d.ts | 17 +- .../web_renderer/src/styles/ValdiWebStyles.ts | 6 +- .../src/styles/requiresUnitlessNumber.ts | 3 +- .../com/snap/valdi/support/SupportFonts.kt | 33 + .../attributes/AttributesBindingContext.kt | 15 +- .../impl/TextViewAttributesBinder.kt | 13 +- .../attributes/impl/fonts/DefaultFonts.kt | 47 +- .../impl/richtext/AttributedText.kt | 6 + .../impl/richtext/AttributedTextCpp.kt | 15 + .../impl/richtext/DashedUnderlineSpan.kt | 10 + .../impl/richtext/DottedUnderlineSpan.kt | 40 ++ .../impl/richtext/FontAttributes.kt | 16 +- .../impl/richtext/PatternUnderlineSpan.kt | 87 +++ .../impl/richtext/RichTextConverter.kt | 5 + .../impl/richtext/TextDecoration.kt | 2 + .../impl/richtext/TextViewHelper.kt | 82 ++- .../snap/valdi/modules/DrawingModuleImpl.kt | 2 + .../com/snap/valdi/views/ValdiEditText.kt | 5 + valdi/src/valdi/android/AttributedTextJNI.hpp | 20 + .../Drawing/SCValdiDrawingModuleFactory.m | 3 +- .../src/valdi/ios/SCValdiAttributesBinder.mm | 16 +- .../valdi/ios/Text/NSAttributedString+Valdi.h | 1 + .../valdi/ios/Text/NSAttributedString+Valdi.m | 70 +- .../valdi/ios/Text/SCValdiAttributedText.h | 10 +- .../valdi/ios/Text/SCValdiAttributedText.mm | 17 +- .../valdi/ios/Text/SCValdiFontAttributes.h | 5 + .../valdi/ios/Text/SCValdiFontAttributes.m | 44 +- valdi/src/valdi/ios/Text/SCValdiFontManager.m | 18 +- valdi/src/valdi/ios/Views/SCValdiTextField.m | 10 +- valdi/src/valdi/ios/Views/SCValdiTextView.m | 1 + .../valdi/macos/Views/SCValdiMacOSTextField.m | 27 +- .../Attributes/TextAttributeValueParser.cpp | 296 ++++++-- .../Layers/Classes/TextLayerClass.cpp | 26 +- .../Layers/Classes/TextLayerClass.hpp | 2 + .../Utils/AttributedTextParser.cpp | 22 + valdi/test/integration/Runtime_tests.cpp | 54 +- valdi/test/ios/SCValdiRuntimeTests.mm | 123 ++++ .../java/attributes/FontAttributesTest.kt | 26 + .../modules/test/src/TextAttribute.tsx | 31 +- .../cpp/Attributes/TextAttributeValue.hpp | 35 +- 65 files changed, 2622 insertions(+), 535 deletions(-) create mode 100644 snap_drawing/test/utils/TestFontUtils.cpp create mode 100644 snap_drawing/test/utils/TestFontUtils.hpp create mode 100644 snap_drawing/testdata/NotoSans-LICENSE.txt create mode 100644 snap_drawing/testdata/NotoSans-Regular.ttf create mode 100644 valdi/src/java/com/snap/valdi/attributes/impl/richtext/DashedUnderlineSpan.kt create mode 100644 valdi/src/java/com/snap/valdi/attributes/impl/richtext/DottedUnderlineSpan.kt create mode 100644 valdi/src/java/com/snap/valdi/attributes/impl/richtext/PatternUnderlineSpan.kt create mode 100644 valdi/test/java/attributes/FontAttributesTest.kt diff --git a/apps/helloworld/src/valdi/hello_world/src/HelloWorldApp.tsx b/apps/helloworld/src/valdi/hello_world/src/HelloWorldApp.tsx index ca2264b1..5fc61f73 100644 --- a/apps/helloworld/src/valdi/hello_world/src/HelloWorldApp.tsx +++ b/apps/helloworld/src/valdi/hello_world/src/HelloWorldApp.tsx @@ -1,7 +1,7 @@ import { Component } from 'valdi_core/src/Component'; import { Style } from 'valdi_core/src/Style'; import { systemFont } from 'valdi_core/src/SystemFont'; -import { Label, ScrollView } from 'valdi_tsx/src/NativeTemplateElements'; +import { Label, Layout, ScrollView, View } from 'valdi_tsx/src/NativeTemplateElements'; import res from '../res'; import { onRootComponentCreated } from './CppModule'; @@ -33,9 +33,47 @@ export class App extends Component { console.log('Hello World onRender!!!'); - - - ; @@ -48,9 +86,33 @@ const styles = { height: '100%', }), + content: new Style({ + alignItems: 'center', + width: '100%', + }), + title: new Style