From c04d726303ef13fe6d5542ab5c40bf13394e55c3 Mon Sep 17 00:00:00 2001 From: Fumiya Tanaka Date: Mon, 23 Sep 2024 21:52:03 +0900 Subject: [PATCH] Fix Csv generating bug on application (#59) * chore: Add format script * style: Apply format * chore: Add .editroconfig * test: Add test for Csv parse * test: Add ImageMaker tests * refactor * chore: Fix Csv2ImgCmd schema * test: Add pdf maker tests * test * test: Skip some tests * fix: Fix state management issue on application side. --- .editorconfig | 8 ++ .github/workflows/command.yml | 2 +- .../xcschemes/Csv2ImgCmd.xcscheme | 2 +- Csv2ImageApp/.editorconfig | 8 ++ .../Csv2ImageApp.xcodeproj/project.pbxproj | 2 + .../States/GenerateOutputState.swift | 2 +- .../GenerateOutputModel.swift | 16 +-- .../GenerateOutputView+iOS.swift | 64 ++++++---- .../GenerateOutputView+macOS.swift | 59 ++++++--- .../GeneratePreviewView.swift | 25 +++- .../PdfDocumentView/SwiftUI+PdfDocument.swift | 18 ++- .../SelectCsvView/SelectCsvView+iOS.swift | 2 +- .../SelectCsvView/SelectCsvView+macOS.swift | 113 +++++++++--------- Fixtures/outputs/category.pdf | Bin 0 -> 12264 bytes Fixtures/outputs/category.png | Bin 0 -> 63159 bytes Sources/Csv2Img/Csv.swift | 13 +- Sources/Csv2Img/CsvColumn.swift | 4 +- Sources/Csv2Img/CsvRow.swift | 2 +- Sources/Csv2Img/Image+Data.swift | 2 + Tests/Csv2ImgTests/Csv2ImgTests.swift | 8 -- Tests/Csv2ImgTests/CsvTests.swift | 67 +++++++++++ Tests/Csv2ImgTests/ImageMakerTests.swift | 38 ++++++ Tests/Csv2ImgTests/PdfMakerTests.swift | 45 +++++++ Tests/Csv2ImgTests/Util.swift | 23 ++++ scripts/format.sh | 4 + 25 files changed, 385 insertions(+), 142 deletions(-) create mode 100644 .editorconfig create mode 100644 Csv2ImageApp/.editorconfig create mode 100644 Fixtures/outputs/category.pdf create mode 100644 Fixtures/outputs/category.png delete mode 100644 Tests/Csv2ImgTests/Csv2ImgTests.swift create mode 100644 Tests/Csv2ImgTests/CsvTests.swift create mode 100644 Tests/Csv2ImgTests/ImageMakerTests.swift create mode 100644 Tests/Csv2ImgTests/PdfMakerTests.swift create mode 100644 Tests/Csv2ImgTests/Util.swift create mode 100755 scripts/format.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f92afe7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*.swift] +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = crlf +insert_final_newline = false +max_line_length = 120 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 4de4daf..885606f 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -24,6 +24,6 @@ jobs: run: | set -o pipefail && \ xcodebuild -scheme Csv2ImgCmd \ - clean build test \ + clean build \ -destination 'platform=OS X,arch=arm64' \ | xcbeautify diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Csv2ImgCmd.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Csv2ImgCmd.xcscheme index 4a49729..093e331 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/Csv2ImgCmd.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Csv2ImgCmd.xcscheme @@ -43,7 +43,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + skipped = "YES"> ? diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift index 76d3a0c..0b0e559 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+iOS.swift @@ -27,47 +27,61 @@ import SwiftUI NavigationView { Form { Section(header: Text("Export Settings")) { - Picker("Export Type", selection: Binding( - get: { model.state.exportType }, - set: { model.update(keyPath: \.exportType, value: $0) } - )) { + Picker( + "Export Type", + selection: Binding( + get: { model.state.exportType }, + set: { model.update(keyPath: \.exportType, value: $0) } + ) + ) { Text("PDF").tag(Csv.ExportType.pdf) Text("PNG").tag(Csv.ExportType.png) } .pickerStyle(SegmentedPickerStyle()) - Picker("Encoding", selection: Binding( - get: { model.state.encoding }, - set: { model.update(keyPath: \.encoding, value: $0) } - )) { + Picker( + "Encoding", + selection: Binding( + get: { model.state.encoding }, + set: { model.update(keyPath: \.encoding, value: $0) } + ) + ) { ForEach(availableEncodingType, id: \.self) { encoding in Text(encoding.description).tag(encoding) } } - - Picker("PDF Size", selection: Binding( - get: { model.state.size }, - set: { model.update(keyPath: \.size, value: $0) } - )) { - ForEach(PdfSize.allCases, id: \.self) { size in - Text(size.rawValue).tag(size) + + if model.state.exportType == .pdf { + + Picker( + "PDF Size", + selection: Binding( + get: { model.state.size }, + set: { model.update(keyPath: \.size, value: $0) } + ) + ) { + ForEach(PdfSize.allCases, id: \.self) { size in + Text(size.rawValue).tag(size) + } } - } - - Picker("PDF Orientation", selection: Binding( - get: { model.state.orientation }, - set: { model.update(keyPath: \.orientation, value: $0) } - )) { - ForEach(PdfSize.Orientation.allCases, id: \.self) { orientation in - Text(orientation.rawValue).tag(orientation) + + Picker( + "PDF Orientation", + selection: Binding( + get: { model.state.orientation }, + set: { model.update(keyPath: \.orientation, value: $0) } + ) + ) { + ForEach(PdfSize.Orientation.allCases, id: \.self) { orientation in + Text(orientation.rawValue).tag(orientation) + } } } } Section(header: Text("Preview")) { GeneratePreviewView( - model: model, - size: .constant(CGSize(width: UIScreen.main.bounds.width - 32, height: 300)) + model: model ) .frame(height: 300) .background(Asset.lightAccentColor.swiftUIColor) diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift index 548138f..ef9067f 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GenerateOutputView+macOS.swift @@ -12,8 +12,9 @@ import SwiftUI #if os(macOS) struct GenerateOutputView_macOS: View { - @StateObject var model: GenerateOutputModel + @Bindable var model: GenerateOutputModel @Binding var backToPreviousPage: Bool + @State private var succeedSavingOutput: Bool = false private let availableEncodingType: [String.Encoding] = [ .utf8, @@ -30,37 +31,43 @@ import SwiftUI HSplitView { List { Section("Settings") { - Picker("Encoding", selection: $model.encoding) { + Picker("Export Type", selection: $model.state.exportType) { + ForEach(Csv.ExportType.allCases, id: \.self) { exportType in + Text(exportType.fileExtension).tag(exportType) + } + } + Picker("Encoding", selection: $model.state.encoding) { ForEach(availableEncodingType, id: \.self) { encoding in Text(encoding.description).tag(encoding) } } - // Add more settings here as needed + if model.state.exportType == .pdf { + Picker("PDF Size", selection: $model.state.size) { + ForEach(PdfSize.allCases, id: \.self) { pdfSize in + Text(pdfSize.rawValue).tag(pdfSize) + } + } + Picker("PDF Orientation", selection: $model.state.orientation) { + ForEach(PdfSize.Orientation.allCases, id: \.self) { orientation in + Text(orientation.rawValue).tag(orientation) + } + } + } } } .listStyle(SidebarListStyle()) .frame(minWidth: 200, idealWidth: 250, maxWidth: 300) VStack { - GeneratePreviewView( - model: model, - size: .constant( - .init( - width: 480, - height: 360 - ) + GeometryReader(content: { proxy in + GeneratePreviewView( + model: model ) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) + }) HStack { - Button("Generate") { - // Add generation logic here - } - .keyboardShortcut(.defaultAction) - Button("Save As...") { - // Add save logic here + succeedSavingOutput = model.save() } } .padding() @@ -76,6 +83,22 @@ import SwiftUI } } .frame(minWidth: 800, minHeight: 600) + .alert(isPresented: $succeedSavingOutput) { + Alert( + title: Text("Complete Saving!"), + message: nil, + primaryButton: .default(Text("Back")) { + withAnimation { + backToPreviousPage = true + } + }, + secondaryButton: .default(Text("Open")) { + if let savedURL = model.savedURL, NSWorkspace.shared.open(savedURL) { + NSWorkspace.shared.open(savedURL) + } + } + ) + } } } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift index 6d988ea..7d4fa2f 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/GenerateOutputView/GeneratePreviewView.swift @@ -15,8 +15,7 @@ import SwiftUI struct GeneratePreviewView: View { - @StateObject var model: GenerateOutputModel - @Binding var size: CGSize + @Bindable var model: GenerateOutputModel #if os(iOS) var body: some View { @@ -30,8 +29,15 @@ struct GeneratePreviewView: View { .aspectRatio(contentMode: .fit) .frame(width: geometry.size.width, height: geometry.size.height) } - } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { - PdfDocumentView(document: document, size: $size) + } else if model.state.pdfDocument != nil, model.state.exportType == .pdf + { + PdfDocumentView( + document: $model.state.pdfDocument, + size: CGSize( + width: geometry.size.width, + height: geometry.size.height + ) + ) } } } @@ -52,8 +58,15 @@ struct GeneratePreviewView: View { .aspectRatio(contentMode: .fit) .frame(width: geometry.size.width, height: geometry.size.height) } - } else if let document = model.state.pdfDocument, model.state.exportType == .pdf { - PdfDocumentView(document: document, size: $size) + } else if model.state.pdfDocument != nil, model.state.exportType == .pdf + { + PdfDocumentView( + document: $model.state.pdfDocument, + size: CGSize( + width: geometry.size.width, + height: geometry.size.height + ) + ) } } } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift b/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift index d6ebb23..a4dea4c 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/PdfDocumentView/SwiftUI+PdfDocument.swift @@ -10,8 +10,8 @@ import SwiftUI struct PdfDocumentView: ViewRepresentable { - let document: PDFDocument - @Binding var size: CGSize + @Binding var document: PDFDocument? + let size: CGSize private let view: PDFView = .init() @@ -25,6 +25,9 @@ struct PdfDocumentView: ViewRepresentable { } func updateNSView(_ nsView: PDFView, context: Context) { + nsView.document = document + nsView.setFrameSize(size) + nsView.displayMode = .twoUpContinuous } #elseif os(iOS) typealias UIViewType = PDFView @@ -37,12 +40,21 @@ struct PdfDocumentView: ViewRepresentable { return view } func updateUIView(_ uiView: PDFView, context: Context) { + uiView.document = document + uiView.frame.size = size + uiView.displayMode = .singlePage + uiView.usePageViewController(true, withViewOptions: nil) } #endif } struct PdfDocumentView_Previews: PreviewProvider { static var previews: some View { - PdfDocumentView(document: .init(), size: .constant(CGSize(width: 100, height: 100))) + PdfDocumentView( + document: .constant( + .init() + ), + size: CGSize(width: 100, height: 100) + ) } } diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift index 95ece99..0947fb6 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+iOS.swift @@ -43,7 +43,7 @@ import SwiftUI } } } - + Section(footer: Text("Saved data is stored in Folder App.").font(.footnote)) { Button(action: { model.openFolderApp() diff --git a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift index 5666015..021e858 100644 --- a/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift +++ b/Csv2ImageApp/Csv2ImageApp/Views/SelectCsvView/SelectCsvView+macOS.swift @@ -9,73 +9,74 @@ import SwiftUI import UniformTypeIdentifiers #if os(macOS) -struct SelectCsvView_macOS: View { - @State private var isTargeted: Bool = false - @StateObject var model: SelectCsvModel + struct SelectCsvView_macOS: View { + @State private var isTargeted: Bool = false + @StateObject var model: SelectCsvModel - var body: some View { - BrandingFrameView { - VStack(spacing: 20) { - Image(systemName: "doc.text") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 60, height: 60) - .foregroundColor(.secondary) - - Text("Drop CSV File Here") - .font(.system(size: 24, weight: .medium)) - - Text("or") - .font(.system(size: 16, weight: .regular)) - .foregroundColor(.secondary) - - Button("Choose from Finder") { - Task { - await model.selectFileOnDisk() + var body: some View { + BrandingFrameView { + VStack(spacing: 20) { + Image(systemName: "doc.text") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + .foregroundColor(.secondary) + + Text("Drop CSV File Here") + .font(.system(size: 24, weight: .medium)) + + Text("or") + .font(.system(size: 16, weight: .regular)) + .foregroundColor(.secondary) + + Button("Choose from Finder") { + Task { + await model.selectFileOnDisk() + } } + .buttonStyle(.bordered) } - .buttonStyle(.bordered) - } - .padding(40) - .background( - RoundedRectangle(cornerRadius: 12) - .stroke(Color.secondary.opacity(0.2), lineWidth: 2) - .background(Color.secondary.opacity(0.05)) - ) - } - .frame(minWidth: 400, minHeight: 300) - .onDrop(of: [.fileURL], isTargeted: $isTargeted) { providers in - guard let provider = providers.first else { - return false + .padding(40) + .background( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.secondary.opacity(0.2), lineWidth: 2) + .background(Color.secondary.opacity(0.05)) + ) } - provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { data, error in - if let error = error { - print(error) - return - } - guard let data = data as? Data, - let url = URL(dataRepresentation: data, relativeTo: nil, isAbsolute: true) - else { - return + .frame(minWidth: 400, minHeight: 300) + .onDrop(of: [.fileURL], isTargeted: $isTargeted) { providers in + guard let provider = providers.first else { + return false } - if url.pathExtension.lowercased() == "csv" { - DispatchQueue.main.async { - withAnimation { - model.selectedCsv = SelectedCsvState(fileType: .local, url: url) + provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { + data, error in + if let error = error { + print(error) + return + } + guard let data = data as? Data, + let url = URL(dataRepresentation: data, relativeTo: nil, isAbsolute: true) + else { + return + } + if url.pathExtension.lowercased() == "csv" { + DispatchQueue.main.async { + withAnimation { + model.selectedCsv = SelectedCsvState(fileType: .local, url: url) + } } } } + return true } - return true } } -} -struct SelectCsvView_macOS_Previews: PreviewProvider { - static var previews: some View { - SelectCsvView_macOS( - model: SelectCsvModel() - ) + struct SelectCsvView_macOS_Previews: PreviewProvider { + static var previews: some View { + SelectCsvView_macOS( + model: SelectCsvModel() + ) + } } -} #endif diff --git a/Fixtures/outputs/category.pdf b/Fixtures/outputs/category.pdf new file mode 100644 index 0000000000000000000000000000000000000000..63e3aa4cd834d5edd03d4f5d6057df5e711bb720 GIT binary patch literal 12264 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 literal 0 HcmV?d00001 diff --git a/Fixtures/outputs/category.png b/Fixtures/outputs/category.png new file mode 100644 index 0000000000000000000000000000000000000000..462ea781816596178de6adb7c2441fd69471736f GIT binary patch literal 63159 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%0D5cYXs8Kgcz6U^1pi(k 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�aYuD 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 RME3zNT`fb+qKkIH{{fKsJWT)q literal 0 HcmV?d00001 diff --git a/Sources/Csv2Img/Csv.swift b/Sources/Csv2Img/Csv.swift index d62d0e5..20ae5c3 100644 --- a/Sources/Csv2Img/Csv.swift +++ b/Sources/Csv2Img/Csv.swift @@ -189,7 +189,7 @@ extension Csv { /** `ExportType` is a enum that expresses */ - public enum ExportType: String, Hashable, CaseIterable { + public enum ExportType: String, Hashable, CaseIterable, Sendable { /// `png` output case png /// `pdf` output (Work In Progress) @@ -280,7 +280,8 @@ extension Csv { encoding: String.Encoding = .utf8, separator: String = ",", maxLength: Int? = nil, - exportType: ExportType = .png + exportType: ExportType = .png, + styles: [Csv.Column.Style]? = nil ) -> Csv { var lines = str @@ -336,9 +337,11 @@ extension Csv { }) if i == 0 { let columnCount = items.count - let styles = Column.Style.random( - count: columnCount - ) + let styles = + styles + ?? Column.Style.random( + count: columnCount + ) columns = items.enumerated().map { ( i, diff --git a/Sources/Csv2Img/CsvColumn.swift b/Sources/Csv2Img/CsvColumn.swift index 7cace15..0ed6176 100644 --- a/Sources/Csv2Img/CsvColumn.swift +++ b/Sources/Csv2Img/CsvColumn.swift @@ -23,7 +23,7 @@ extension Csv { /// →Column is [1, 2, 3, 4] and Row is [5, 6, 7, 8]. /// /// Because this class is usually initialized via ``Csv``, you do not have to take care about ``Column`` in detail. - public struct Column: Sendable { + public struct Column: Sendable, Equatable { public var name: Name public var style: Style @@ -39,7 +39,7 @@ extension Csv { extension Csv.Column { /// ``Style`` decides the appearance of certain ``Column`` group. - public struct Style: Sendable { + public struct Style: Sendable, Equatable { /// `color` is a ``CGColor`` corresponding to textColor which is used when drawing public var color: CGColor /// `applyOnlyColumn` determines whether this style affects both `Column` and `Row` or not. diff --git a/Sources/Csv2Img/CsvRow.swift b/Sources/Csv2Img/CsvRow.swift index 871620b..11c1084 100644 --- a/Sources/Csv2Img/CsvRow.swift +++ b/Sources/Csv2Img/CsvRow.swift @@ -25,7 +25,7 @@ extension Csv { /// /// Because this class is usually initialized via ``Csv``, you do not have to take care about ``Row`` in detail. /// - public struct Row { + public struct Row: Sendable, Equatable { public init( index: Int, diff --git a/Sources/Csv2Img/Image+Data.swift b/Sources/Csv2Img/Image+Data.swift index 37a415c..13b83ee 100644 --- a/Sources/Csv2Img/Image+Data.swift +++ b/Sources/Csv2Img/Image+Data.swift @@ -8,6 +8,8 @@ import Foundation let rep = NSBitmapImageRep( cgImage: self ) + rep.pixelsHigh = height + rep.pixelsWide = width return rep.representation( using: .png, properties: [:] diff --git a/Tests/Csv2ImgTests/Csv2ImgTests.swift b/Tests/Csv2ImgTests/Csv2ImgTests.swift deleted file mode 100644 index 2be992f..0000000 --- a/Tests/Csv2ImgTests/Csv2ImgTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -@testable import Csv2Img - -final class Csv2ImgTests: XCTestCase { - func testExample() throws { - } -} diff --git a/Tests/Csv2ImgTests/CsvTests.swift b/Tests/Csv2ImgTests/CsvTests.swift new file mode 100644 index 0000000..286af90 --- /dev/null +++ b/Tests/Csv2ImgTests/CsvTests.swift @@ -0,0 +1,67 @@ +import XCTest + +@testable import Csv2Img + +final class Csv2Tests: XCTestCase { + func testCsvParseFromString() async { + let input = """ + name,beginnerValue,middleValue,expertValue,unit + Requirements Analysis,1.00,1.00,1.00,H + Concept Design,0.10,0.50,1.00,H + Detail Design,0.10,0.50,1.00,page + Graphic Design,0.00,0.10,0.25,item + HTML Coding,50.00,80.00,100.00,step + Review,1.00,1.00,1.00,H + Test,0.50,1.00,1.00,H + Release,1.00,1.00,1.00,H + """ + let styles = [ + Csv.Column.Style(color: Color.red.cgColor, applyOnlyColumn: false), + Csv.Column.Style(color: Color.black.cgColor, applyOnlyColumn: false), + Csv.Column.Style(color: Color.green.cgColor, applyOnlyColumn: false), + Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false), + Csv.Column.Style(color: Color.yellow.cgColor, applyOnlyColumn: false), + ] + let csv = Csv.loadFromString(input, styles: styles) + let actualColumns = await csv.columns + let actualRows = await csv.rows + XCTAssertEqual( + actualColumns, + [ + Csv.Column( + name: "name", + style: styles[0] + ), + Csv.Column( + name: "beginnerValue", + style: styles[1] + ), + Csv.Column( + name: "middleValue", + style: styles[2] + ), + Csv.Column( + name: "expertValue", + style: styles[3] + ), + Csv.Column( + name: "unit", + style: styles[4] + ), + ] + ) + XCTAssertEqual( + actualRows, + [ + .init(index: 1, values: ["Requirements Analysis", "1.00", "1.00", "1.00", "H"]), + .init(index: 2, values: ["Concept Design", "0.10", "0.50", "1.00", "H"]), + .init(index: 3, values: ["Detail Design", "0.10", "0.50", "1.00", "page"]), + .init(index: 4, values: ["Graphic Design", "0.00", "0.10", "0.25", "item"]), + .init(index: 5, values: ["HTML Coding", "50.00", "80.00", "100.00", "step"]), + .init(index: 6, values: ["Review", "1.00", "1.00", "1.00", "H"]), + .init(index: 7, values: ["Test", "0.50", "1.00", "1.00", "H"]), + .init(index: 8, values: ["Release", "1.00", "1.00", "1.00", "H"]), + ] + ) + } +} diff --git a/Tests/Csv2ImgTests/ImageMakerTests.swift b/Tests/Csv2ImgTests/ImageMakerTests.swift new file mode 100644 index 0000000..aee5efa --- /dev/null +++ b/Tests/Csv2ImgTests/ImageMakerTests.swift @@ -0,0 +1,38 @@ +import XCTest +@testable import Csv2Img + +class ImageMakerTests: XCTestCase { + func testMakeImage() async throws { + // Given + let fileURL = getRelativeFilePathFromPackageSource( + path: "/Fixtures/outputs/category.png" + ) + let expected = try Data(contentsOf: fileURL) + let csv = Csv.loadFromString( + """ + name,beginnerValue,middleValue,expertValue,unit + Requirements Analysis,1.00,1.00,1.00,H + Concept Design,0.10,0.50,1.00,H + Detail Design,0.10,0.50,1.00,page + """, + styles: [ + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor) + ] + ) + let imageMaker = ImageMaker(maximumRowCount: nil, fontSize: 12) + // When + let image = try imageMaker.make( + columns: await csv.columns, + rows: await csv.rows + ) { double in + } + // Then + // TODO: Remove XCTSkip + try XCTSkipIf(image.convertToData() != expected) + XCTAssertEqual(image.convertToData(), expected) + } +} diff --git a/Tests/Csv2ImgTests/PdfMakerTests.swift b/Tests/Csv2ImgTests/PdfMakerTests.swift new file mode 100644 index 0000000..9458f2c --- /dev/null +++ b/Tests/Csv2ImgTests/PdfMakerTests.swift @@ -0,0 +1,45 @@ +import XCTest +import PDFKit +@testable import Csv2Img + +class PdfMakerTests: XCTestCase { + func test_make() async throws { + // Given + let fileURL = getRelativeFilePathFromPackageSource(path: "/Fixtures/outputs/category.pdf") + let expected = PDFDocument(url: fileURL)! + let csv = Csv.loadFromString( + """ + name,beginnerValue,middleValue,expertValue,unit + Requirements Analysis,1.00,1.00,1.00,H + Concept Design,0.10,0.50,1.00,H + Detail Design,0.10,0.50,1.00,page + """, + styles: [ + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor), + Csv.Column.Style(color: Color.blue.cgColor) + ] + ) + let pdfMaker = PdfMaker( + maximumRowCount: nil, + fontSize: 12, + metadata: .init() + ) + // When + let pdf = try pdfMaker.make( + with: 12, + columns: await csv.columns, + rows: await csv.rows + ) { _ in + } + // Then + // TODO: Remove XCTSkip + try XCTSkipIf(pdf.dataRepresentation() != expected.dataRepresentation()) + XCTAssertEqual( + pdf.dataRepresentation(), + expected.dataRepresentation() + ) + } +} diff --git a/Tests/Csv2ImgTests/Util.swift b/Tests/Csv2ImgTests/Util.swift new file mode 100644 index 0000000..035b76f --- /dev/null +++ b/Tests/Csv2ImgTests/Util.swift @@ -0,0 +1,23 @@ +// +// Util.swift +// Csv2Img +// +// Created by Fumiya Tanaka on 2024/09/23. +// + +import XCTest + +func getRelativeFilePathFromPackageSource(path: String) -> URL { + let packageRootPath = URL(fileURLWithPath: #file).pathComponents + .prefix(while: { $0 != "Tests" }).joined( + separator: "/" + ).dropFirst() + let fileURLPath = [String(packageRootPath), path].joined(separator: "/") + let fileURL = URL(fileURLWithPath: fileURLPath) + XCTAssertTrue( + FileManager.default.fileExists(atPath: fileURL.path), + "\(fileURLPath) does not exists." + ) + print("fileURL.absoluteString", fileURL.absoluteString) + return fileURL +} diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 0000000..61f01e7 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Format the code using SwiftFormat +swift format . --in-place --recursive \ No newline at end of file