import AppKit import DocsKit import RastrilloApple import SwiftUI @MainActor private enum SmokeRun { static var started = false } @main struct DocumentWindowSmoke: App { @State private var windows = DocsWindows() var body: some Scene { Window("Smoke Home", id: "home") { Launcher(windows: windows) } WindowGroup("Smoke Document", id: "document", for: String.self) { _ in DocsWorkspace(client: NativeHTTP(origin: URL(string: ProcessInfo.processInfo.environment["DOCS_SMOKE_ORIGIN"]!)!), signOut: {}, documentID: "fixture", newDocument: ProcessInfo.processInfo.environment["DOCS_SMOKE_DRAFT"] == "1") }.defaultSize(width: 1120, height: 820) .restorationBehavior(.disabled) .commands { DocsEditorCommands(); DocsWindowCommands(windows: windows) } } } private struct Launcher: View { let windows: DocsWindows @Environment(\.openWindow) private var openWindow var body: some View { Text("Synthetic document window smoke test").frame(width: 400, height: 200) .task { guard !SmokeRun.started else { return } SmokeRun.started = true NSApp.setActivationPolicy(.regular) openWindow(id: "document", value: "fixture") if ProcessInfo.processInfo.environment["DOCS_SMOKE_DRAFT"] == "1" { let start = ContinuousClock.now var draftEditor: NSTextView? var draftWindow: NSWindow? for _ in 0..<20 { if let window = NSApp.windows.first(where: { $0.title == docsMacString("Untitled document") }), let editor = window.contentView.flatMap(findEditor) { draftWindow = window; draftEditor = editor; break } try? await Task.sleep(for: .milliseconds(50)) } guard let window = draftWindow, let editor = draftEditor else { fatalError("Local editor waited for server creation") } precondition(start.duration(to: .now) < .seconds(1)) NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(nil); window.makeFirstResponder(editor) editor.insertText("Instant draft", replacementRange: NSRange(location: 0, length: 0)) for _ in 0..<80 { if window.title == "Window smoke fixture" { break } try? await Task.sleep(for: .milliseconds(50)) } precondition(window.title == "Window smoke fixture") precondition(window.contentView.flatMap(findEditor) === editor, "Server attachment replaced the editor") precondition(editor.string.contains("Instant draft"), "Creation lost locally typed text") print("PASS: draft editor appears before delayed creation and keeps its text view and typing") await exercise(window, editor: editor) return } for _ in 0..<100 { if let window = NSApp.windows.first(where: { $0.title == "Window smoke fixture" }), let editor = window.contentView.flatMap(findEditor), !editor.string.isEmpty { await exercise(window, editor: editor) return } try? await Task.sleep(for: .milliseconds(100)) } fatalError("Document editor did not load") } } @MainActor private func findEditor(_ view: NSView) -> NSTextView? { if let text = view as? NSTextView, text.accessibilityIdentifier() == "docs.editor" { return text } return view.subviews.lazy.compactMap(findEditor).first } @MainActor private func menuItem(_ title: String, in menu: NSMenu) -> NSMenuItem? { for item in menu.items { if item.title == title { return item } if let submenu = item.submenu, let match = menuItem(title, in: submenu) { return match } } return nil } @MainActor private func exercise(_ window: NSWindow, editor: NSTextView) async { NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(nil) window.makeFirstResponder(editor) editor.setSelectedRange(NSRange(location: 1, length: 2)) try? await Task.sleep(for: .milliseconds(500)) let original = editor.string for step in 0..<8 { try? await Task.sleep(for: .milliseconds(200)) guard let menu = NSApp.mainMenu, let initial = menuItem(docsMacString("Document inspector"), in: menu), let submenu = initial.menu else { fatalError("Document inspector command unavailable") } submenu.delegate?.menuNeedsUpdate?(submenu) submenu.update() guard let item = menuItem(docsMacString("Document inspector"), in: menu), item.isEnabled else { fatalError("Inspector command disabled in document window") } let beforeWidth = editor.enclosingScrollView!.frame.width item.menu?.performActionForItem(at: item.menu!.index(of: item)) try? await Task.sleep(for: .milliseconds(200)) precondition(abs(editor.enclosingScrollView!.frame.width - beforeWidth) > 200, "Inspector did not change editor space") window.setContentSize(NSSize(width: step.isMultiple(of: 2) ? 900 : 1200, height: 760)) try? await Task.sleep(for: .milliseconds(200)) window.contentView?.layoutSubtreeIfNeeded() precondition(window.contentView.flatMap(findEditor) === editor, "Inspector recreated editor") precondition(editor.string == original && editor.selectedRange() == NSRange(location: 1, length: 2), "Panel lost content or selection") } // Save while the title field is still active, without submitting Return. if let menu = NSApp.mainMenu, let rename = menuItem("Rename document", in: menu) { rename.menu?.delegate?.menuNeedsUpdate?(rename.menu!) rename.menu?.update() guard let currentRename = menuItem("Rename document", in: menu), currentRename.isEnabled else { fatalError("Rename disabled in writable document") } currentRename.menu?.performActionForItem(at: currentRename.menu!.index(of: currentRename)) try? await Task.sleep(for: .milliseconds(200)) func findTitle(_ view: NSView) -> NSTextField? { if let field = view as? NSTextField, field.isEditable { return field } return view.subviews.lazy.compactMap(findTitle).first } guard let root = window.contentView?.superview, let field = findTitle(root) else { fatalError("Editable header title missing") } field.selectText(nil) guard let input = field.currentEditor() as? NSTextView else { fatalError("Title has no field editor") } input.selectAll(nil); input.insertText("Renamed from header", replacementRange: input.selectedRange()) try? await Task.sleep(for: .milliseconds(200)) guard let save = menuItem(docsMacString("Save"), in: menu) else { fatalError("Save unavailable") } save.menu?.performActionForItem(at: save.menu!.index(of: save)) try? await Task.sleep(for: .milliseconds(300)) precondition(window.title == "Renamed from header", "Save lost the active title input") print("PASS: Save commits active header title without Return") } else { fatalError("Rename command missing") } window.makeFirstResponder(editor) editor.setSelectedRange(NSRange(location: editor.string.utf16.count - 1, length: 0)) NSApp.activate(ignoringOtherApps: true) window.makeKeyAndOrderFront(nil) window.makeFirstResponder(editor) editor.scrollRangeToVisible(editor.selectedRange()) try? await Task.sleep(for: .milliseconds(100)) func indicators(_ view: NSView) -> [NSView] { (view is NSTextInsertionIndicator ? [view] : []) + view.subviews.flatMap(indicators) } func record(_ phase: String) { let manager = editor.layoutManager! manager.ensureLayout(for: editor.textContainer!) let glyph = manager.glyphIndexForCharacter(at: min(editor.selectedRange().location, editor.string.utf16.count - 1)) let line = manager.lineFragmentRect(forGlyphAt: glyph, effectiveRange: nil) let rect = editor.firstRect(forCharacterRange: editor.selectedRange(), actualRange: nil) precondition(editor.textContainerOrigin == .zero, "Native caret origin drifted") precondition(rect.height > 0, "Caret clipped outside editor bounds") precondition(editor.bounds.maxY >= line.maxY, "Editor shrank below current line") let visible = indicators(window.contentView!).filter { !$0.isHidden } precondition(visible.count == 1, "Expected exactly one visible native insertion indicator; count=\(visible.count), phase=\(phase), editable=\(editor.isEditable), attached=\(editor.window === window), key=\(window.isKeyWindow), responder=\(String(describing: window.firstResponder))") for indicator in visible { let actual = indicator.convert(indicator.bounds, to: editor) precondition(abs(actual.minY - line.minY) < 1, "Native insertion indicator disagrees with glyphs") } } for character in "\nCaret stability after Enter.\nSecond line.\nThird line." { window.makeKeyAndOrderFront(nil) window.makeFirstResponder(editor) let before = editor.selectedRange().location let typed = character == "\n" ? "\r" : String(character) let event = NSEvent.keyEvent(with: .keyDown, location: .zero, modifierFlags: [], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: typed, charactersIgnoringModifiers: typed, isARepeat: false, keyCode: character == "\n" ? 36 : 0)! editor.keyDown(with: event) record("immediate") let immediate = editor.firstRect(forCharacterRange: editor.selectedRange(), actualRange: nil) try? await Task.sleep(for: .milliseconds(100)) precondition(editor.selectedRange().location == before + String(character).utf16.count, "Typing moved selection unexpectedly") record("settled") let settled = editor.firstRect(forCharacterRange: editor.selectedRange(), actualRange: nil) precondition(abs(immediate.minY - settled.minY) < 1, "Caret jumped after publication") } if let path = ProcessInfo.processInfo.environment["DOCS_SMOKE_SCREENSHOT"], let view = window.contentView, let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) { view.cacheDisplay(in: view.bounds, to: bitmap) try? bitmap.representation(using: .png, properties: [:])?.write(to: URL(fileURLWithPath: path)) } print("PASS: document loads, inspector toggles, window resizes, editor and selection persist, Enter and typing keep caret geometry stable") NSApp.windows.first(where: { $0.title == "Smoke Home" })?.close() window.makeKeyAndOrderFront(nil) guard let menu = NSApp.mainMenu, let appMenu = menu.items.first?.submenu else { fatalError("App menu missing") } appMenu.delegate?.menuNeedsUpdate?(appMenu) appMenu.update() guard let accounts = menuItem(docsMacString("Accounts"), in: appMenu), accounts.isEnabled, let signOut = menuItem(docsMacString("Sign out"), in: appMenu), !signOut.isEnabled else { fatalError("Account commands missing or unsafe in document window") } accounts.menu!.performActionForItem(at: accounts.menu!.index(of: accounts)) try? await Task.sleep(for: .milliseconds(300)) precondition(windows.accountsRequested, "Accounts action did not reach launcher") precondition(NSApp.windows.contains { $0.title == "Smoke Home" && $0.isVisible }, "Accounts did not reopen Home") print("PASS: account commands live in app menu and Accounts reopens closed Home") NSApp.terminate(nil) } }