Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/release-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,36 @@ jobs:
xcrun simctl list runtimes
- run: ./Scripts/release-readiness.sh
- run: ./Scripts/ipad-icloud-tests.sh
- name: Report XCTest results
if: always()
run: |
python3 - <<'PY'
import glob
import json
import subprocess

for root in ("/tmp/scriptwidget-release-readiness", "/tmp/scriptwidget-ipad-icloud-tests"):
for path in sorted(glob.glob(f"{root}/*/Logs/Test/*.xcresult")):
print(f"XCTest result: {path}", flush=True)
result = subprocess.run(
["xcrun", "xcresulttool", "get", "test-results", "summary", "--path", path],
capture_output=True, text=True,
)
if result.returncode:
print(result.stderr, flush=True)
continue
summary = json.loads(result.stdout)
print(json.dumps({key: summary.get(key) for key in (
"title", "result", "passedTests", "failedTests", "skippedTests", "testFailures"
)}, indent=2), flush=True)
PY
- name: Upload XCTest diagnostics
if: always()
uses: actions/upload-artifact@v7
with:
name: xctest-results-${{ github.run_attempt }}
path: |
/tmp/scriptwidget-release-readiness/*/Logs/Test/
/tmp/scriptwidget-ipad-icloud-tests/*/Logs/Test/
if-no-files-found: warn
retention-days: 7
13 changes: 13 additions & 0 deletions Scripts/macos-widget-link-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -eu

ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
TEST_DIR=$(mktemp -d /tmp/scriptwidget-link-tests.XXXXXX)
trap 'rm -rf "$TEST_DIR"' EXIT

swiftc -module-cache-path "$TEST_DIR/module-cache" \
"$ROOT/macOS/ScriptWidgetMac/App/AppDelegate.swift" \
"$ROOT/Shared/ScriptWidgetRuntime/Widget/Runtime/DeepLinkDefine.swift" \
"$ROOT/Tests/MacWidgetLinkTests/main.swift" \
-o "$TEST_DIR/widget-link-tests"
"$TEST_DIR/widget-link-tests"
1 change: 1 addition & 0 deletions Scripts/release-readiness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ git diff --exit-code -- \
macOS/ScriptWidgetMac/StudioEditor.bundle

if [ "${SCRIPTWIDGET_SKIP_XCODE:-0}" != "1" ]; then
sh Scripts/macos-widget-link-tests.sh
xcodebuild -quiet -project macOS/ScriptWidgetMac.xcodeproj -scheme ScriptWidgetRuntimeTests \
-destination 'platform=macOS' -derivedDataPath "$DERIVED_DATA/macos-tests" \
CODE_SIGNING_ALLOWED=NO test
Expand Down
32 changes: 32 additions & 0 deletions Tests/MacWidgetLinkTests/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import AppKit
import Foundation

// Isolate the app delegate from iCloud caching for this routing regression test.
final class StubScriptManager {
func precacheAllScripts() {}
}
let sharedScriptManager = StubScriptManager()

let delegate = AppDelegate()
var openedURLs: [URL] = []
delegate.openWidgetURL = { openedURLs.append($0) }

let externalURLs = [
URL(string: "https://xnu.app/scriptwidget")!,
URL(string: "scriptable:///run/Name")!,
URL(fileURLWithPath: "/tmp/widget-card.html"),
URL(string: "mailto:example@example.com")!
]
let internalURLs = [
kDeepLinkDefaultURL,
URL(string: "WIDGET-DEEPLINK://default")!,
URL(string: "scriptwidget://open")!,
URL(string: "SCRIPTWIDGET://open")!,
URL(string: "relative/path")!
]

delegate.application(NSApplication.shared, open: internalURLs + externalURLs)
precondition(openedURLs == externalURLs, "Forward external URLs in order and ignore internal/schemeless URLs")
delegate.application(NSApplication.shared, open: [])
precondition(openedURLs == externalURLs, "An empty URL batch should do nothing")
print("✓ macOS widget URL routing passed")
15 changes: 11 additions & 4 deletions Tests/ScriptWidgetRuntimeTests/RuntimeExecutionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,17 +260,24 @@ final class RuntimeExecutionTests: XCTestCase {
}
}

func testConcurrentRuntimesKeepEnvironmentStateIsolated() {
func testConcurrentRuntimesKeepEnvironmentStateIsolated() throws {
let source = "$render(<text>{$getenv(\"widget-param\")}</text>);"
// Exercise concurrent environment isolation without racing eight cold
// Babel compilations of identical source on constrained CI simulators.
// Each concurrent execution still gets its own package and JSContext.
let warmup = makeRuntime(environments: ["widget-size": "medium", "widget-param": "warmup"])
.executeJSXSyncForWidget(source)
XCTAssertNil(warmup.1)
XCTAssertEqual(collectText(try XCTUnwrap(warmup.0)), "warmup")

let lock = NSLock()
var rendered: [Int: String] = [:]
var failures: [String] = []

DispatchQueue.concurrentPerform(iterations: 8) { index in
let token = "runtime-\(index)"
let runtime = makeRuntime(environments: ["widget-size": "medium", "widget-param": token])
let (element, error) = runtime.executeJSXSyncForWidget(
"$render(<text>{$getenv(\"widget-param\")}</text>);"
)
let (element, error) = runtime.executeJSXSyncForWidget(source)
lock.lock()
defer { lock.unlock() }
if let error {
Expand Down
13 changes: 13 additions & 0 deletions macOS/ScriptWidgetMac/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ import Foundation
import AppKit

class AppDelegate: NSObject, NSApplicationDelegate {
var openWidgetURL: (URL) -> Void = { url in
NSWorkspace.shared.open(url)
}

func application(_ application: NSApplication, open urls: [URL]) {
for url in urls {
guard let scheme = url.scheme?.lowercased(),
scheme != kDeepLinkDefaultScheme,
scheme != "scriptwidget" else { continue }
openWidgetURL(url)
}
}

func applicationDidFinishLaunching(_ notification: Notification) {
print("did finish launching")
// The embedded editor is served via a WKURLSchemeHandler — no
Expand Down
10 changes: 3 additions & 7 deletions macOS/ScriptWidgetMac/Create/CreateGuideView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,10 @@ struct CreateGuideView: View {
}
} label: {
HStack(spacing: 12) {
Image(systemName: "sparkles")
.font(.title)
.foregroundStyle(.white)
Image(systemName: "sparkle")
.font(.system(size: 32, weight: .light))
.foregroundStyle(.primary)
.frame(width: 44, height: 44)
.background(LinearGradient(colors: [.purple, .blue],
startPoint: .topLeading,
endPoint: .bottomTrailing))
.clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 2) {
Text("Generate with AI").font(.headline)
Text("Describe your widget and let the AI build it.")
Expand Down
Loading