From 6251f2490bb4b8910511ba1b465dc5ce2e4926fa Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 19 Oct 2026 10:22:14 +0100 Subject: [PATCH] new tests --- DBSyn/App.swift | 2 +- DBSyn/LaravelMigrationView.swift | 52 +++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/DBSyn/App.swift b/DBSyn/App.swift index b8da462..f8e04f4 100644 --- a/DBSyn/App.swift +++ b/DBSyn/App.swift @@ -22,7 +22,7 @@ struct DBSynApp: App { } } .environmentObject(model) - .sheet(isPresented: $showingMigrationGenerator) { LaravelMigrationView(model: model) } + .sheet(isPresented: $showingMigrationGenerator) { LaravelMigrationView(model: model, onClose: { showingMigrationGenerator = false }) } .frame(minWidth: 800, minHeight: 520) } .defaultSize(width: 1200, height: 800) diff --git a/DBSyn/LaravelMigrationView.swift b/DBSyn/LaravelMigrationView.swift index e19f146..a12f1d8 100644 --- a/DBSyn/LaravelMigrationView.swift +++ b/DBSyn/LaravelMigrationView.swift @@ -19,10 +19,15 @@ enum LaravelMigrationGenerator { } guard depth == 0 else { throw NSError(domain: "DBSyn.Migration", code: 2, userInfo: [NSLocalizedDescriptionKey: "Unbalanced CREATE TABLE parentheses."]) } let body = String(ddl[start..primary('\(columns[0])');") @@ -71,6 +76,10 @@ enum LaravelMigrationGenerator { var args = "'\(col)'" if method == "string" || method == "char", let range = type.range(of: #"\(\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].filter(\.isNumber) } if method == "decimal", let range = type.range(of: #"\(\s*\d+\s*,\s*\d+\s*\)"#, options: .regularExpression) { args += ", " + type[range].dropFirst().dropLast() } + if upper.contains("AUTO_INCREMENT") && primaryKeys == [col] && method == "bigInteger" { + lines.append(" $table->id('\(col)');") + continue + } var expr = " $table->\(method)(\(args))" if upper.contains("UNSIGNED") { expr += "->unsigned()" } if !upper.contains("NOT NULL") { expr += "->nullable()" } @@ -126,6 +135,9 @@ struct LaravelMigrationView: View { @State private var tableName = "" @State private var chosenDatabase = "" @State private var isLoading = false + @State private var tableSearch = "" + @State private var statusText = "" + var onClose: () -> Void = {} var body: some View { VStack(alignment: .leading, spacing: 14) { HStack { @@ -135,6 +147,8 @@ struct LaravelMigrationView: View { Text("Convert a MySQL table or CREATE TABLE SQL into a Laravel migration.").foregroundStyle(.secondary) } Spacer() + Button(action: onClose) { Image(systemName: "xmark.circle.fill").font(.title2).foregroundStyle(.secondary) } + .buttonStyle(.plain).help("Close Migration Generator") } VStack(alignment: .leading, spacing: 10) { HStack(spacing: 12) { @@ -158,13 +172,16 @@ struct LaravelMigrationView: View { TextField("Enter a table name", text: $tableName) .frame(maxWidth: 330) if !availableTables.isEmpty { - Picker("Choose table", selection: $tableName) { - Text("Choose table…").tag("") - ForEach(availableTables, id: \.self) { name in - Text(name).tag(name) + Menu { + TextField("Search tables", text: $tableSearch) + Divider() + ForEach(filteredTables.prefix(100), id: \.self) { name in + Button(name) { tableName = name } } + if filteredTables.isEmpty { Text("No matching tables") } + } label: { + Label("Browse Tables (\(availableTables.count))", systemImage: "magnifyingglass") } - .labelsHidden() .frame(maxWidth: 230) } Button(isLoading ? "Loading…" : "Load Table") { loadTable() } @@ -188,10 +205,12 @@ struct LaravelMigrationView: View { } } if !errorText.isEmpty { Text(errorText).foregroundStyle(.red).font(.callout) } + if !statusText.isEmpty { Text(statusText).foregroundStyle(.green).font(.callout) } HStack { Text("Review indexes, foreign keys, defaults and unsupported MySQL types before running a migration.").font(.caption).foregroundStyle(.secondary) Spacer() - Button("Generate") { generate() }.buttonStyle(.borderedProminent) + Button("Generate / Refresh") { generate() }.buttonStyle(.borderedProminent) + .disabled(sourceSQL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) Button("Copy PHP") { NSPasteboard.general.clearContents(); NSPasteboard.general.setString(generated, forType: .string) }.disabled(generated.isEmpty) Button("Save Migration…") { save() }.disabled(generated.isEmpty) } @@ -203,7 +222,8 @@ struct LaravelMigrationView: View { chosenDatabase = model.completionDatabase } } - .onChange(of: chosenDatabase) { _, _ in tableName = "" } + .onChange(of: chosenDatabase) { _, _ in tableName = ""; tableSearch = "" } + .onChange(of: sourceSQL) { _, _ in statusText = "Source changed — select Generate / Refresh to update PHP." } } private var databaseOptions: [String] { Array(Set(model.databases + model.schemaTables.keys + [model.completionDatabase].filter { !$0.isEmpty })).sorted() @@ -211,9 +231,19 @@ struct LaravelMigrationView: View { private var availableTables: [String] { model.schemaTables[chosenDatabase] ?? [] } + private var filteredTables: [String] { + let query = tableSearch.trimmingCharacters(in: .whitespacesAndNewlines) + return query.isEmpty ? availableTables : availableTables.filter { $0.localizedCaseInsensitiveContains(query) } + } private func generate() { - do { generated = try LaravelMigrationGenerator.generate(sourceSQL); errorText = "" } - catch { errorText = error.localizedDescription } + do { + generated = try LaravelMigrationGenerator.generate(sourceSQL) + errorText = "" + statusText = "Migration generated — review the PHP, then Copy or Save." + } catch { + errorText = error.localizedDescription + statusText = "" + } } private func openFile() { let panel = NSOpenPanel(); panel.allowedContentTypes = [UTType(filenameExtension: "sql") ?? .plainText, .plainText]; panel.allowsMultipleSelection = false @@ -248,7 +278,7 @@ struct LaravelMigrationView: View { let name = tableName.isEmpty ? "table" : tableName.lowercased() panel.nameFieldStringValue = "\(prefix)_\(name)_table.php" if panel.runModal() == .OK, let url = panel.url { - do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = "" } + do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = ""; statusText = "Saved: \(url.lastPathComponent)" } catch { errorText = error.localizedDescription } } }