152 lines
7.8 KiB
Swift
152 lines
7.8 KiB
Swift
import AppKit
|
|
import ConjureKit
|
|
import Testing
|
|
@testable import Conjure
|
|
|
|
@Test @MainActor
|
|
func livePaletteRepaintsNotesWithoutChangingContentSelectionOrUndo() throws {
|
|
_ = NSApplication.shared
|
|
let originalPalette = ThemePalette.loaded
|
|
defer {
|
|
ThemePalette.loaded = originalPalette
|
|
NotificationCenter.default.post(name: ThemePalette.didChangeNotification, object: nil)
|
|
}
|
|
let directory = try liveViewThemeDirectory()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
ThemePalette.reload(from: directory)
|
|
|
|
let editor = NotesTextView(frame: NSRect(x: 0, y: 0, width: 500, height: 300))
|
|
editor.isRichText = true
|
|
let delegate = LiveThemeTextDelegate()
|
|
editor.delegate = delegate
|
|
let document = NSMutableAttributedString(string: "plain code\nlet answer = 42\n")
|
|
let inline = NSRange(location: 6, length: 4)
|
|
let code = (document.string as NSString).paragraphRange(for: NSRange(location: 11, length: 0))
|
|
document.addAttribute(NoteAttributes.inlineCode, value: true, range: inline)
|
|
document.addAttribute(NoteAttributes.bold, value: true, range: NSRange(location: 0, length: 5))
|
|
document.addAttribute(NoteAttributes.block, value: "code", range: code)
|
|
document.addAttribute(NoteAttributes.language, value: "swift", range: code)
|
|
editor.setDocumentContents(document)
|
|
editor.beginInsertionSession()
|
|
editor.insertText("!", replacementRange: NSRange(location: 0, length: 0))
|
|
let selection = NSRange(location: 2, length: 3)
|
|
editor.setSelectedRange(selection)
|
|
let contents = editor.string
|
|
let grouping = editor.undoManager?.groupingLevel
|
|
let oldAccent = editor.insertionPointColor
|
|
let codeColor = editor.textStorage?.attribute(.foregroundColor, at: code.location + 1, effectiveRange: nil) as? NSColor
|
|
let boldFont = editor.textStorage?.attribute(.font, at: 1, effectiveRange: nil) as? NSFont
|
|
let changeCount = delegate.changeCount
|
|
|
|
try writeLiveViewTheme(in: directory)
|
|
#expect(ThemePalette.reload(from: directory))
|
|
|
|
#expect(editor.insertionPointColor == ThemePalette.accent)
|
|
#expect(editor.insertionPointColor != oldAccent)
|
|
#expect(editor.selectedTextAttributes[.backgroundColor] as? NSColor == ThemePalette.selectionFill)
|
|
#expect(editor.textStorage?.attribute(.foregroundColor, at: 1, effectiveRange: nil) as? NSColor == ThemePalette.text)
|
|
#expect(editor.textStorage?.attribute(.foregroundColor, at: inline.location + 1, effectiveRange: nil) as? NSColor == ThemePalette.accent)
|
|
#expect(editor.textStorage?.attribute(.backgroundColor, at: inline.location + 1, effectiveRange: nil) as? NSColor == ThemePalette.selectionFill)
|
|
#expect(editor.textStorage?.attribute(.backgroundColor, at: code.location + 1, effectiveRange: nil) as? NSColor == ThemePalette.selectionFill)
|
|
#expect(editor.textStorage?.attribute(.foregroundColor, at: code.location + 1, effectiveRange: nil) as? NSColor != codeColor)
|
|
#expect(editor.textStorage?.attribute(.font, at: 1, effectiveRange: nil) as? NSFont == boldFont)
|
|
#expect(editor.textStorage?.attribute(NoteAttributes.inlineCode, at: inline.location + 1, effectiveRange: nil) as? Bool == true)
|
|
#expect(editor.textStorage?.attribute(NoteAttributes.language, at: code.location + 1, effectiveRange: nil) as? String == "swift")
|
|
#expect(editor.string == contents)
|
|
#expect(editor.selectedRange() == selection)
|
|
#expect(editor.undoManager?.groupingLevel == grouping)
|
|
#expect(delegate.changeCount == changeCount)
|
|
editor.endInsertionSession()
|
|
#expect(editor.undoManager?.canUndo == true)
|
|
editor.undoManager?.undo()
|
|
#expect(editor.string == document.string)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func livePaletteRepaintsLauncherAndFavoriteBadgesWithoutResettingSearch() throws {
|
|
_ = NSApplication.shared
|
|
let originalPalette = ThemePalette.loaded
|
|
defer {
|
|
ThemePalette.loaded = originalPalette
|
|
NotificationCenter.default.post(name: ThemePalette.didChangeNotification, object: nil)
|
|
}
|
|
let directory = try liveViewThemeDirectory()
|
|
defer { try? FileManager.default.removeItem(at: directory) }
|
|
ThemePalette.reload(from: directory)
|
|
let suite = "Conjure.LiveThemeViews.\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: suite))
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let launcher = LauncherWindowController(
|
|
history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) }),
|
|
defaults: defaults, notesDirectory: directory.appendingPathComponent("notes"))
|
|
let window = try #require(launcher.window)
|
|
let content = try #require(window.contentView)
|
|
let views = liveThemedDescendants(content)
|
|
let search = try #require(views.compactMap { $0 as? NSSearchField }.first)
|
|
let table = try #require(views.compactMap { $0 as? NSTableView }.first)
|
|
search.stringValue = "notes"
|
|
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
|
let rows = table.numberOfRows
|
|
let selected = table.selectedRowIndexes
|
|
let placeholder = search.placeholderAttributedString?.string
|
|
window.makeFirstResponder(search)
|
|
let fieldEditor = try #require(search.currentEditor() as? NSTextView)
|
|
fieldEditor.setSelectedRange(NSRange(location: 1, length: 2))
|
|
let selection = fieldEditor.selectedRange()
|
|
let pill = try #require(views.compactMap { $0 as? ActionPill }.first)
|
|
let oldBorder = pill.layer?.borderColor
|
|
let cell = ApplicationCell(frame: .zero)
|
|
cell.kindLabel.attributedStringValue = ApplicationCell.favoriteCommandKind
|
|
cell.setNumberHint("3", animated: false)
|
|
let numberHint = try #require(liveThemedDescendants(cell.numberHint).compactMap { $0 as? NSTextField }.first)
|
|
|
|
try writeLiveViewTheme(in: directory)
|
|
#expect(ThemePalette.reload(from: directory))
|
|
|
|
#expect(search.stringValue == "notes")
|
|
#expect(search.placeholderAttributedString?.string == placeholder)
|
|
#expect(table.numberOfRows == rows)
|
|
#expect(table.selectedRowIndexes == selected)
|
|
#expect(search.currentEditor() === fieldEditor)
|
|
#expect(fieldEditor.selectedRange() == selection)
|
|
#expect(search.textColor == ThemePalette.text)
|
|
#expect(fieldEditor.insertionPointColor == ThemePalette.accent)
|
|
#expect(search.placeholderAttributedString?.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor == ThemePalette.secondaryText)
|
|
#expect(pill.layer?.borderColor == ThemePalette.accent.withAlphaComponent(0.35).cgColor)
|
|
#expect(pill.layer?.borderColor != oldBorder)
|
|
#expect(cell.kindLabel.attributedStringValue.attribute(.foregroundColor, at: cell.kindLabel.attributedStringValue.length - 1, effectiveRange: nil) as? NSColor == ThemePalette.secondaryText)
|
|
#expect(!cell.numberHint.isHidden)
|
|
#expect(numberHint.stringValue == "3")
|
|
#expect(numberHint.layer?.backgroundColor == ThemePalette.keyBackground.cgColor)
|
|
}
|
|
|
|
private func liveViewThemeDirectory() throws -> URL {
|
|
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
return directory
|
|
}
|
|
|
|
private func writeLiveViewTheme(in directory: URL) throws {
|
|
try Data("""
|
|
[colors]
|
|
background = "#ffffff"
|
|
text = "#112233"
|
|
secondaryText = "#445566"
|
|
accent = "#1234ab"
|
|
glow = "#abcdef"
|
|
keyBackground = "#778899"
|
|
error = "#aa0011"
|
|
""".utf8).write(to: directory.appendingPathComponent("config.toml"))
|
|
}
|
|
|
|
@MainActor
|
|
private func liveThemedDescendants(_ view: NSView) -> [NSView] {
|
|
[view] + view.subviews.flatMap { liveThemedDescendants($0) }
|
|
}
|
|
|
|
@MainActor
|
|
private final class LiveThemeTextDelegate: NSObject, NSTextViewDelegate {
|
|
var changeCount = 0
|
|
func textDidChange(_ notification: Notification) { changeCount += 1 }
|
|
}
|