diff --git a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java new file mode 100644 index 0000000000..118f81d0d7 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.json; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +/** + * Simple JSON formatter which reformats the file according to the org.json library's default pretty-printing, but has no ability to customise more than the indentation size. + */ +public final class JsonSimpleStep { + private static final String MAVEN_COORDINATE = "org.json:json:"; + private static final String DEFAULT_VERSION = "20210307"; + + public static FormatterStep create(int indent, Provisioner provisioner) { + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("json", () -> new State(indent, provisioner), State::toFormatter); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final int indentSpaces; + private final JarState jarState; + + private State(int indent, Provisioner provisioner) throws IOException { + this.indentSpaces = indent; + this.jarState = JarState.from(MAVEN_COORDINATE + DEFAULT_VERSION, provisioner); + } + + FormatterFunc toFormatter() { + Method objectToString; + Method arrayToString; + Constructor objectConstructor; + Constructor arrayConstructor; + try { + ClassLoader classLoader = jarState.getClassLoader(); + Class jsonObject = classLoader.loadClass("org.json.JSONObject"); + Class[] constructorArguments = new Class[]{String.class}; + objectConstructor = jsonObject.getConstructor(constructorArguments); + objectToString = jsonObject.getMethod("toString", int.class); + + Class jsonArray = classLoader.loadClass("org.json.JSONArray"); + arrayConstructor = jsonArray.getConstructor(constructorArguments); + arrayToString = jsonArray.getMethod("toString", int.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new IllegalStateException("There was a problem preparing org.json dependencies", e); + } + + return s -> { + String prettyPrinted = null; + if (s.isEmpty()) { + prettyPrinted = s; + } + if (s.startsWith("{")) { + try { + Object parsed = objectConstructor.newInstance(s); + prettyPrinted = objectToString.invoke(parsed, indentSpaces) + "\n"; + } catch (InvocationTargetException ignored) { + // ignore if we cannot convert to JSON string + } + } + if (s.startsWith("[")) { + try { + Object parsed = arrayConstructor.newInstance(s); + prettyPrinted = arrayToString.invoke(parsed, indentSpaces) + "\n"; + } catch (InvocationTargetException ignored) { + // ignore if we cannot convert to JSON string + } + } + + if (prettyPrinted == null) { + throw new AssertionError("Invalid JSON file provided"); + } + + return prettyPrinted; + }; + } + } + + private JsonSimpleStep() { + // cannot be directly instantiated + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/package-info.java b/lib/src/main/java/com/diffplug/spotless/json/package-info.java new file mode 100644 index 0000000000..0d62356d77 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@ReturnValuesAreNonnullByDefault +package com.diffplug.spotless.extra.json.java; + +import javax.annotation.ParametersAreNonnullByDefault; + +import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault; diff --git a/testlib/src/main/resources/json/cucumberJsonSampleAfter.json b/testlib/src/main/resources/json/cucumberJsonSampleAfter.json new file mode 100644 index 0000000000..dc0f6f90f7 --- /dev/null +++ b/testlib/src/main/resources/json/cucumberJsonSampleAfter.json @@ -0,0 +1,550 @@ +[ + { + "line": 2, + "elements": [ + { + "line": 7, + "name": "Activate Credit Card", + "description": "Perfect background", + "keyword": "Background", + "type": "background", + "steps": [ + { + "result": { + "duration": 99107447000, + "status": "passed" + }, + "embeddings": [ + { + "data": "", + "mime_type": "image/url" + }, + { + "data": "", + "media": {"type": "text/plain"} + } + ], + "line": 8, + "name": "I have a new credit card", + "match": {"location": "ATMScenario.I_have_a_new_credit_card()"}, + "keyword": "Given " + }, + { + "result": { + "duration": 9520000, + "status": "passed" + }, + "line": 9, + "name": "My credit card is described as follow:", + "match": {"location": "ATMScenario.My_credit_card_is_described_as_follow"}, + "keyword": "And ", + "doc_string": { + "content_type": "", + "line": 10, + "value": "{\n\"issuer\": {\n\"name\": \"Real Bank Inc.\",\n\"isn:\": \"RB55800093842N\"\n},\n\"card_number\": \"4896 0215 8478 6325\",\n\"holder\": \"A guy\"\n}" + } + }, + { + "result": { + "duration": 7040000, + "status": "passed" + }, + "line": 18, + "name": "I confirm my pin number", + "match": {"location": "ATMScenario.I_confirm_my_pin_number()"}, + "keyword": "When ", + "rows": [ + { + "cells": [ + "Müller", + "Deutschland" + ], + "line": 2 + }, + { + "cells": [ + "Nováková", + "Česko" + ], + "line": 3 + }, + { + "cells": [ + "Kovačević", + "Hrvatska" + ], + "line": 4 + }, + { + "cells": [ + "Παπαδόπουλος", + "Παπαδόπουλος" + ], + "line": 5 + }, + { + "cells": [ + "罗/羅", + "中國" + ], + "line": 6 + } + ] + }, + { + "result": { + "duration": 111111, + "status": "passed" + }, + "line": 19, + "name": "the card should be activated", + "match": {"location": "ATMScenario.the_card_should_be_activated()"}, + "keyword": "Then " + } + ] + }, + { + "line": 33, + "name": "Account has ", + "description": "Account holder withdraws cash", + "id": "account-holder-withdraws-cash;account-has-'sufficient-funds';;2", + "after": [{ + "result": { + "duration": 60744700, + "error_message": "Completed", + "status": "passed" + }, + "match": {"location": "MachineFactory.timeout()"} + }], + "keyword": "Scenario Outline", + "type": "scenario", + "steps": [ + { + "result": { + "duration": 17007000, + "status": "passed" + }, + "line": 23, + "name": "the account balance is 100", + "match": { + "arguments": [{ + "val": "100", + "offset": 23 + }], + "location": "ATMScenario.createAccount(int)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 33444444, + "status": "passed" + }, + "line": 24, + "name": "the card is valid", + "match": { + "arguments": [{ + "val": "", + "offset": 0 + }], + "location": "ATMScenario.createCreditCard()" + }, + "keyword": "And " + }, + { + "result": { + "duration": 44333333, + "status": "passed" + }, + "line": 25, + "name": "100 is contained in the machine", + "match": { + "arguments": [{ + "val": "100", + "offset": 0 + }], + "location": "ATMScenario.createATM(int)" + }, + "matchedColumns": [1], + "keyword": "And " + }, + { + "result": { + "duration": 11000001, + "status": "passed" + }, + "line": 26, + "name": "the Account Holder requests 10, entering PIN 1234", + "match": { + "arguments": [ + { + "val": "10", + "offset": 28 + }, + { + "val": "1234", + "offset": 45 + } + ], + "location": "ATMScenario.requestMoney(int)" + }, + "matchedColumns": [2], + "keyword": "When " + }, + { + "result": { + "duration": 3220000, + "status": "passed" + }, + "line": 27, + "name": "the ATM should dispense 10 monetary units", + "match": { + "arguments": [ + { + "val": "10", + "offset": 24 + }, + { + "val": "", + "offset": 0 + } + ], + "location": "ATMScenario.checkMoney(int)" + }, + "matchedColumns": [3], + "keyword": "Then " + }, + { + "result": { + "duration": 30000000, + "status": "passed" + }, + "line": 28, + "name": "the account balance should be 90", + "match": {"location": "ATMScenario.checkBalance(int)"}, + "arguments": [{"rows": [ + {"cells": [ + "max", + "min" + ]}, + {"cells": [ + "20", + "3" + ]} + ]}], + "matchedColumns": [2], + "keyword": "And " + } + ], + "tags": [ + { + "line": 21, + "name": "@fast" + }, + { + "line": 1, + "name": "@featureTag" + }, + { + "line": 21, + "name": "@checkout" + } + ] + } + ], + "name": "1st feature", + "description": "This is description of the feature", + "id": "account-holder-withdraws-cash", + "keyword": "Feature", + "uri": "net/masterthought/example(s)/ATM:東京.feature", + "tags": [{ + "line": 1, + "name": "@featureTag" + }] + }, + { + "line": 1, + "elements": [ + { + "before": [ + { + "output": ["System version: beta3"], + "result": { + "duration": 10744700, + "status": "passed" + }, + "match": {"location": "MachineFactory.findCashMachine()"} + }, + { + "result": { + "duration": 1000001, + "error_message": " \n", + "status": "failed" + }, + "match": {"location": "MachineFactory.wait()"} + } + ], + "line": 19, + "name": "Account may not have sufficient funds", + "description": "Account holder withdraws more cash", + "id": "account-holder-withdraws-more-cash;account-has-sufficient-funds;;2", + "after": [{ + "result": { + "duration": 64700000, + "error_message": "Undefined step", + "status": "undefined" + }, + "embeddings": [{ + "data": "", + "mime_type": "image/png" + }], + "match": {"location": "any.error()"} + }], + "keyword": "Scenario Outline", + "type": "scenario", + "steps": [ + { + "result": {"status": "undefined"}, + "before": [{ + "embeddings": [{ + "data": "", + "mime_type": "text/plain" + }], + "result": { + "duration": 410802047, + "status": "failed" + } + }], + "line": 7, + "name": "the account balance is 100", + "match": {"arguments": [ + { + "val": "100", + "offset": 23 + }, + {} + ]}, + "matchedColumns": [0], + "keyword": "Given " + }, + { + "result": { + "duration": 13000, + "status": "passed" + }, + "line": 8, + "name": "the card is valid", + "match": { + "arguments": [{ + "val": "", + "offset": 17 + }], + "location": "ATMScenario.createCreditCard()" + }, + "after": [{ + "result": { + "duration": 410802048, + "status": "passed" + }, + "match": {"location": "StepHook.afterStep()"} + }], + "keyword": "And " + }, + { + "result": { + "duration": 36000, + "status": "passed" + }, + "line": 9, + "name": "the machine contains 100", + "match": { + "arguments": [{ + "val": "100", + "offset": 21 + }], + "location": "ATMScenario.createATM(int)" + }, + "matchedColumns": [1], + "keyword": "And " + }, + { + "result": { + "duration": 32000, + "status": "passed" + }, + "line": 10, + "name": "the Account Holder requests 20", + "match": { + "arguments": [{ + "val": "20", + "offset": 28 + }], + "location": "ATMScenario.requestMoney(int)" + }, + "matchedColumns": [2], + "keyword": "When " + }, + { + "result": { + "duration": 36000, + "status": "passed" + }, + "line": 11, + "name": "the ATM should dispense 20", + "match": { + "arguments": [{ + "val": "20", + "offset": 24 + }], + "location": "ATMScenario.checkMoney(int)" + }, + "matchedColumns": [3], + "keyword": "Then " + }, + { + "result": { + "duration": 1933000, + "error_message": "java.lang.AssertionError: \nExpected: is <80>\n got: <90>\n\n\tat org.junit.Assert.assertThat(Assert.java:780)\n\tat org.junit.Assert.assertThat(Assert.java:738)\n\tat net.masterthought.example.ATMScenario.checkBalance(ATMScenario.java:69)\n\tat ✽.And the account balance should be 90(net/masterthought/example/ATMK.feature:12)\n", + "status": "skipped" + }, + "embeddings": [ + { + "data": "", + "mime_type": "image/png", + "name": "Some PNG image" + }, + { + "data": "", + "mime_type": "image/jpeg" + }, + { + "data": "", + "mime_type": "text/plain" + }, + { + "data": "", + "mime_type": "text/html", + "name": "Some HTML embedding" + }, + { + "data": "", + "mime_type": "text/xml" + }, + { + "data": "", + "mime_type": "image/svg+xml" + }, + { + "data": "", + "mime_type": "js" + }, + { + "data": "", + "mime_type": "text/plain" + }, + { + "data": "", + "mime_type": "text/csv" + }, + { + "data": "", + "mime_type": "video/mp4" + } + ], + "line": 12, + "name": "the account balance should be 90", + "match": { + "arguments": [{ + "val": "90", + "offset": 30 + }], + "location": "ATMScenario.checkBalance(int)" + }, + "matchedColumns": [4], + "keyword": "And " + }, + { + "result": {"status": "pending"}, + "embeddings": [{ + "data": "", + "mime_type": "application/json" + }], + "line": 13, + "name": "the card should be returned", + "match": {"location": "ATMScenario.cardShouldBeReturned()"}, + "keyword": "And " + }, + { + "result": {"status": "skipped"}, + "output": [ + ["Could not connect to the server @Rocky@"], + ["Could not connect to the server @Mike@"] + ], + "line": 14, + "name": "its not implemented", + "match": {"location": "ATMScenario.its_not_implemented()"}, + "keyword": "And " + }, + { + "result": {"status": "failed"}, + "output": [ + "Checkpoints", + 232 + ], + "line": 15, + "name": "the card is valid", + "match": {"location": "ATMScenario.createCreditCard()"}, + "keyword": "And " + }, + { + "result": { + "duration": 90000000, + "status": "ambiguous" + }, + "line": 29, + "name": "the card should be returned", + "match": {"location": "ATMScenario.cardShouldBeReturned()"}, + "keyword": "And " + } + ], + "tags": [{ + "line": 101, + "name": "@checkout" + }] + }, + { + "line": 31, + "name": "Clean-up", + "id": "account-holder-withdraws-more-cash;clean-up", + "keyword": "Scenario", + "type": "scenario", + "steps": [{ + "result": { + "duration": 560000, + "status": "passed" + }, + "line": 32, + "name": "Stream closing", + "keyword": "Given " + }] + }, + { + "line": 35, + "name": "This step has no result...", + "id": "undefined-result", + "keyword": "Scenario", + "type": "scenario", + "steps": [{ + "line": 36, + "name": " - even it should", + "keyword": "Given " + }] + } + ], + "name": "Second feature", + "description": "As an Account Holder\nI want to withdraw cash from an ATM,
so that I can get money when the bank is closed", + "id": "account-holder-withdraws-more-cash", + "keyword": "Feature", + "uri": "net/masterthought/example/ATMK.feature" + } +] diff --git a/testlib/src/main/resources/json/cucumberJsonSampleBefore.json b/testlib/src/main/resources/json/cucumberJsonSampleBefore.json new file mode 100644 index 0000000000..8250630cf7 --- /dev/null +++ b/testlib/src/main/resources/json/cucumberJsonSampleBefore.json @@ -0,0 +1,660 @@ +[ + { + "id": "account-holder-withdraws-cash", + "tags": [ + { + "name": "@featureTag", + "line": 1 + } + ], + "description": "This is description of the feature", + "name": "1st feature", + "keyword": "Feature", + "line": 2, + "elements": [ + { + "description": "Perfect background", + "name": "Activate Credit Card", + "keyword": "Background", + "line": 7, + "steps": [ + { + "result": { + "duration": 99107447000, + "status": "passed" + }, + "name": "I have a new credit card", + "keyword": "Given ", + "line": 8, + "match": { + "location": "ATMScenario.I_have_a_new_credit_card()" + }, + "embeddings": [ + { + "mime_type": "image/url", + "data": "" + }, + { + "data": "", + "media": { + "type": "text/plain" + } + } + ] + }, + { + "result": { + "duration": 9520000, + "status": "passed" + }, + "name": "My credit card is described as follow:", + "keyword": "And ", + "line": 9, + "match": { + "location": "ATMScenario.My_credit_card_is_described_as_follow" + }, + "doc_string": { + "content_type": "", + "line": 10, + "value": "{\n\"issuer\": {\n\"name\": \"Real Bank Inc.\",\n\"isn:\": \"RB55800093842N\"\n},\n\"card_number\": \"4896 0215 8478 6325\",\n\"holder\": \"A guy\"\n}" + } + }, + { + "result": { + "duration": 7040000, + "status": "passed" + }, + "name": "I confirm my pin number", + "keyword": "When ", + "line": 18, + "match": { + "location": "ATMScenario.I_confirm_my_pin_number()" + }, + "rows": [ + { + "cells": [ + "Müller", + "Deutschland" + ], + "line": 2 + }, + { + "cells": [ + "Nováková", + "Česko" + ], + "line": 3 + }, + { + "cells": [ + "Kovačević", + "Hrvatska" + ], + "line": 4 + }, + { + "cells": [ + "Παπαδόπουλος", + "Παπαδόπουλος" + ], + "line": 5 + }, + { + "cells": [ + "罗/羅", + "中國" + ], + "line": 6 + } + ] + }, + { + "result": { + "duration": 111111, + "status": "passed" + }, + "name": "the card should be activated", + "keyword": "Then ", + "line": 19, + "match": { + "location": "ATMScenario.the_card_should_be_activated()" + } + } + ], + "type": "background" + }, + { + "id": "account-holder-withdraws-cash;account-has-\u0027sufficient-funds\u0027;;2", + "tags": [ + { + "name": "@fast", + "line": 21 + }, + { + "name": "@featureTag", + "line": 1 + }, + { + "name": "@checkout", + "line": 21 + } + ], + "description": "Account holder withdraws cash", + "name": "Account has ", + "keyword": "Scenario Outline", + "line": 33, + "steps": [ + { + "result": { + "duration": 17007000, + "status": "passed" + }, + "name": "the account balance is 100", + "keyword": "Given ", + "line": 23, + "match": { + "arguments": [ + { + "val": "100", + "offset": 23 + } + ], + "location": "ATMScenario.createAccount(int)" + } + }, + { + "result": { + "duration": 33444444, + "status": "passed" + }, + "name": "the card is valid", + "keyword": "And ", + "line": 24, + "match": { + "arguments": [ + { + "val": "", + "offset": 0 + } + ], + "location": "ATMScenario.createCreditCard()" + } + }, + { + "result": { + "duration": 44333333, + "status": "passed" + }, + "name": "100 is contained in the machine", + "keyword": "And ", + "line": 25, + "match": { + "arguments": [ + { + "val": "100", + "offset": 0 + } + ], + "location": "ATMScenario.createATM(int)" + }, + "matchedColumns": [ + 1 + ] + }, + { + "result": { + "duration": 11000001, + "status": "passed" + }, + "name": "the Account Holder requests 10, entering PIN 1234", + "keyword": "When ", + "line": 26, + "match": { + "arguments": [ + { + "val": "10", + "offset": 28 + }, + { + "val": "1234", + "offset": 45 + } + ], + "location": "ATMScenario.requestMoney(int)" + }, + "matchedColumns": [ + 2 + ] + }, + { + "result": { + "duration": 3220000, + "status": "passed" + }, + "name": "the ATM should dispense 10 monetary units", + "keyword": "Then ", + "line": 27, + "match": { + "arguments": [ + { + "val": "10", + "offset": 24 + }, + { + "val": "", + "offset": 0 + } + ], + "location": "ATMScenario.checkMoney(int)" + }, + "matchedColumns": [ + 3 + ] + }, + { + "result": { + "duration": 30000000, + "status": "passed" + }, + "name": "the account balance should be 90", + "keyword": "And ", + "line": 28, + "arguments": [ + { + "rows": [ + { + "cells": [ + "max", + "min" + ] + }, + { + "cells": [ + "20", + "3" + ] + } + ] + } + ], + "match": { + "location": "ATMScenario.checkBalance(int)" + }, + "matchedColumns": [ + 2 + ] + } + ], + "type": "scenario", + "after": [ + { + "result": { + "duration": 60744700, + "status": "passed", + "error_message": "Completed" + }, + "match": { + "location": "MachineFactory.timeout()" + } + } + ] + } + ], + "uri": "net/masterthought/example(s)/ATM:東京.feature" + }, + { + "id": "account-holder-withdraws-more-cash", + "description": "As an Account Holder\nI want to withdraw cash from an ATM,
so that I can get money when the bank is closed", + "name": "Second feature", + "keyword": "Feature", + "line": 1, + "elements": [ + { + "id": "account-holder-withdraws-more-cash;account-has-sufficient-funds;;2", + "tags": [ + { + "name": "@checkout", + "line": 101 + } + ], + "before": [ + { + "output": [ + "System version: beta3" + ], + "result": { + "duration": 10744700, + "status": "passed" + }, + "match": { + "location": "MachineFactory.findCashMachine()" + } + }, + { + "result": { + "duration": 1000001, + "status": "failed", + "error_message": " \n" + }, + "match": { + "location": "MachineFactory.wait()" + } + } + ], + "description": "Account holder withdraws more cash", + "name": "Account may not have sufficient funds", + "keyword": "Scenario Outline", + "line": 19, + "steps": [ + { + "result": { + "status": "undefined" + }, + "name": "the account balance is 100", + "keyword": "Given ", + "line": 7, + "match": { + "arguments": [ + { + "val": "100", + "offset": 23 + }, + {} + ] + }, + "matchedColumns": [ + 0 + ], + "before": [ + { + "embeddings": [ + { + "mime_type": "text/plain", + "data": "" + } + ], + "result": { + "duration": 410802047, + "status": "failed" + } + } + ] + }, + { + "result": { + "duration": 13000, + "status": "passed" + }, + "name": "the card is valid", + "keyword": "And ", + "line": 8, + "match": { + "arguments": [ + { + "val": "", + "offset": 17 + } + ], + "location": "ATMScenario.createCreditCard()" + }, + "after": [ + { + "result": { + "duration": 410802048, + "status": "passed" + }, + "match": { + "location": "StepHook.afterStep()" + } + } + ] + }, + { + "result": { + "duration": 36000, + "status": "passed" + }, + "name": "the machine contains 100", + "keyword": "And ", + "line": 9, + "match": { + "arguments": [ + { + "val": "100", + "offset": 21 + } + ], + "location": "ATMScenario.createATM(int)" + }, + "matchedColumns": [ + 1 + ] + }, + { + "result": { + "duration": 32000, + "status": "passed" + }, + "name": "the Account Holder requests 20", + "keyword": "When ", + "line": 10, + "match": { + "arguments": [ + { + "val": "20", + "offset": 28 + } + ], + "location": "ATMScenario.requestMoney(int)" + }, + "matchedColumns": [ + 2 + ] + }, + { + "result": { + "duration": 36000, + "status": "passed" + }, + "name": "the ATM should dispense 20", + "keyword": "Then ", + "line": 11, + "match": { + "arguments": [ + { + "val": "20", + "offset": 24 + } + ], + "location": "ATMScenario.checkMoney(int)" + }, + "matchedColumns": [ + 3 + ] + }, + { + "result": { + "duration": 1933000, + "status": "skipped", + "error_message": "java.lang.AssertionError: \nExpected: is \u003c80\u003e\n got: \u003c90\u003e\n\n\tat org.junit.Assert.assertThat(Assert.java:780)\n\tat org.junit.Assert.assertThat(Assert.java:738)\n\tat net.masterthought.example.ATMScenario.checkBalance(ATMScenario.java:69)\n\tat ✽.And the account balance should be 90(net/masterthought/example/ATMK.feature:12)\n" + }, + "name": "the account balance should be 90", + "keyword": "And ", + "line": 12, + "match": { + "arguments": [ + { + "val": "90", + "offset": 30 + } + ], + "location": "ATMScenario.checkBalance(int)" + }, + "matchedColumns": [ + 4 + ], + "embeddings": [ + { + "mime_type": "image/png", + "data": "", + "name": "Some PNG image" + }, + { + "mime_type": "image/jpeg", + "data": "" + }, + { + "mime_type": "text/plain", + "data": "" + }, + { + "mime_type": "text/html", + "data": "", + "name": "Some HTML embedding" + }, + { + "mime_type": "text/xml", + "data": "" + }, + { + "mime_type": "image/svg+xml", + "data": "" + }, + { + "mime_type": "js", + "data": "" + }, + { + "mime_type": "text/plain", + "data": "" + }, + { + "mime_type": "text/csv", + "data": "" + }, + { + "mime_type": "video/mp4", + "data": "" + } + ] + }, + { + "result": { + "status": "pending" + }, + "name": "the card should be returned", + "keyword": "And ", + "line": 13, + "match": { + "location": "ATMScenario.cardShouldBeReturned()" + }, + "embeddings": [ + { + "mime_type": "application/json", + "data": "" + } + ] + }, + { + "result": { + "status": "skipped" + }, + "name": "its not implemented", + "keyword": "And ", + "line": 14, + "match": { + "location": "ATMScenario.its_not_implemented()" + }, + "output": [ + [ + "Could not connect to the server @Rocky@" + ], + [ + "Could not connect to the server @Mike@" + ] + ] + }, + { + "result": { + "status": "failed" + }, + "name": "the card is valid", + "keyword": "And ", + "line": 15, + "match": { + "location": "ATMScenario.createCreditCard()" + }, + "output": [ + "Checkpoints", + 232 + ] + }, + { + "result": { + "duration": 90000000, + "status": "ambiguous" + }, + "name": "the card should be returned", + "keyword": "And ", + "line": 29, + "match": { + "location": "ATMScenario.cardShouldBeReturned()" + } + } + ], + "type": "scenario", + "after": [ + { + "result": { + "duration": 64700000, + "status": "undefined", + "error_message": "Undefined step" + }, + "match": { + "location": "any.error()" + }, + "embeddings": [ + { + "mime_type": "image/png", + "data": "" + } + ] + } + ] + }, + { + "id": "account-holder-withdraws-more-cash;clean-up", + "name": "Clean-up", + "keyword": "Scenario", + "line": 31, + "steps": [ + { + "result": { + "duration": 560000, + "status": "passed" + }, + "name": "Stream closing", + "keyword": "Given ", + "line": 32 + } + ], + "type": "scenario" + }, + { + "id": "undefined-result", + "name": "This step has no result...", + "keyword": "Scenario", + "line": 35, + "steps": [ + { + "name": " - even it should", + "keyword": "Given ", + "line": 36 + } + ], + "type": "scenario" + } + ], + "uri": "net/masterthought/example/ATMK.feature" + } +] diff --git a/testlib/src/main/resources/json/emptyAfter.json b/testlib/src/main/resources/json/emptyAfter.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/json/emptyBefore.json b/testlib/src/main/resources/json/emptyBefore.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/json/invalidJsonAfter.json b/testlib/src/main/resources/json/invalidJsonAfter.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/json/invalidJsonBefore.json b/testlib/src/main/resources/json/invalidJsonBefore.json new file mode 100644 index 0000000000..0a36b4c415 --- /dev/null +++ b/testlib/src/main/resources/json/invalidJsonBefore.json @@ -0,0 +1,2 @@ +{ +"a": f diff --git a/testlib/src/main/resources/json/nestedObjectAfter.json b/testlib/src/main/resources/json/nestedObjectAfter.json new file mode 100644 index 0000000000..070dfc481e --- /dev/null +++ b/testlib/src/main/resources/json/nestedObjectAfter.json @@ -0,0 +1,11 @@ +{ + "abc": "def", + "obj": { + "arr": [ + 1, + 2, + 3 + ], + "val": 5 + } +} diff --git a/testlib/src/main/resources/json/nestedObjectBefore.json b/testlib/src/main/resources/json/nestedObjectBefore.json new file mode 100644 index 0000000000..37a0e0b90c --- /dev/null +++ b/testlib/src/main/resources/json/nestedObjectBefore.json @@ -0,0 +1,7 @@ +{ "abc": "def", + "obj": { + "arr": [ + 1,2,3 + ], + "val": 5 + } } diff --git a/testlib/src/main/resources/json/notJsonAfter.json b/testlib/src/main/resources/json/notJsonAfter.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/json/notJsonBefore.json b/testlib/src/main/resources/json/notJsonBefore.json new file mode 100644 index 0000000000..358200a574 --- /dev/null +++ b/testlib/src/main/resources/json/notJsonBefore.json @@ -0,0 +1 @@ +#not valid JSON diff --git a/testlib/src/main/resources/json/objectWithNullAfter.json b/testlib/src/main/resources/json/objectWithNullAfter.json new file mode 100644 index 0000000000..5e47a73852 --- /dev/null +++ b/testlib/src/main/resources/json/objectWithNullAfter.json @@ -0,0 +1,4 @@ +{ + "another": 1, + "value": null +} diff --git a/testlib/src/main/resources/json/objectWithNullBefore.json b/testlib/src/main/resources/json/objectWithNullBefore.json new file mode 100644 index 0000000000..f68567c97c --- /dev/null +++ b/testlib/src/main/resources/json/objectWithNullBefore.json @@ -0,0 +1,4 @@ +{ +"value": null, +"another": 1 +} diff --git a/testlib/src/main/resources/json/singletonArrayAfter.json b/testlib/src/main/resources/json/singletonArrayAfter.json new file mode 100644 index 0000000000..fa3d04c724 --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfter.json @@ -0,0 +1,6 @@ +[ + 1, + 2, + 3, + 4 +] diff --git a/testlib/src/main/resources/json/singletonArrayAfter0Spaces.json b/testlib/src/main/resources/json/singletonArrayAfter0Spaces.json new file mode 100644 index 0000000000..8adb9bb604 --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfter0Spaces.json @@ -0,0 +1 @@ +[1,2,3,4] diff --git a/testlib/src/main/resources/json/singletonArrayAfter6Spaces.json b/testlib/src/main/resources/json/singletonArrayAfter6Spaces.json new file mode 100644 index 0000000000..2550a537b5 --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfter6Spaces.json @@ -0,0 +1,6 @@ +[ + 1, + 2, + 3, + 4 +] diff --git a/testlib/src/main/resources/json/singletonArrayAfterTabs.json b/testlib/src/main/resources/json/singletonArrayAfterTabs.json new file mode 100644 index 0000000000..6a8580f45d --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfterTabs.json @@ -0,0 +1,6 @@ +[ + 1, + 2, + 3, + 4 +] diff --git a/testlib/src/main/resources/json/singletonArrayBefore.json b/testlib/src/main/resources/json/singletonArrayBefore.json new file mode 100644 index 0000000000..8290d39198 --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayBefore.json @@ -0,0 +1 @@ +[ 1, 2, 3, 4 ] diff --git a/testlib/src/main/resources/json/singletonObjectAfter.json b/testlib/src/main/resources/json/singletonObjectAfter.json new file mode 100644 index 0000000000..bbcc15e8cc --- /dev/null +++ b/testlib/src/main/resources/json/singletonObjectAfter.json @@ -0,0 +1 @@ +{"a": "b"} diff --git a/testlib/src/main/resources/json/singletonObjectBefore.json b/testlib/src/main/resources/json/singletonObjectBefore.json new file mode 100644 index 0000000000..611670e052 --- /dev/null +++ b/testlib/src/main/resources/json/singletonObjectBefore.json @@ -0,0 +1 @@ +{"a": "b"} diff --git a/testlib/src/main/resources/json/singletonObjectWithArrayAfter.json b/testlib/src/main/resources/json/singletonObjectWithArrayAfter.json new file mode 100644 index 0000000000..39c104af94 --- /dev/null +++ b/testlib/src/main/resources/json/singletonObjectWithArrayAfter.json @@ -0,0 +1 @@ +{"a": [1]} diff --git a/testlib/src/main/resources/json/singletonObjectWithArrayBefore.json b/testlib/src/main/resources/json/singletonObjectWithArrayBefore.json new file mode 100644 index 0000000000..c427907dc0 --- /dev/null +++ b/testlib/src/main/resources/json/singletonObjectWithArrayBefore.json @@ -0,0 +1 @@ +{"a": [1]} diff --git a/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java b/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java new file mode 100644 index 0000000000..fccd563444 --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/json/JsonSimpleStepTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2021 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.json; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.Test; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.SerializableEqualityTester; +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; + +public class JsonSimpleStepTest { + + private static final int INDENT = 4; + + private final FormatterStep step = JsonSimpleStep.create(INDENT, TestProvisioner.mavenCentral()); + private final StepHarness stepHarness = StepHarness.forStep(step); + + @Test + public void cannotProvidedNullProvisioner() { + assertThatThrownBy(() -> JsonSimpleStep.create(INDENT, null)).isInstanceOf(NullPointerException.class).hasMessage("provisioner cannot be null"); + } + + @Test + public void handlesSingletonObject() throws Exception { + doWithResource(stepHarness, "singletonObject"); + } + + @Test + public void handlesSingletonObjectWithArray() throws Exception { + doWithResource(stepHarness, "singletonObjectWithArray"); + } + + @Test + public void handlesNestedObject() throws Exception { + doWithResource(stepHarness, "nestedObject"); + } + + @Test + public void handlesSingletonArray() throws Exception { + doWithResource(stepHarness, "singletonArray"); + } + + @Test + public void handlesEmptyFile() throws Exception { + doWithResource(stepHarness, "empty"); + } + + @Test + public void handlesComplexNestedObject() throws Exception { + doWithResource(stepHarness, "cucumberJsonSample"); + } + + @Test + public void handlesObjectWithNull() throws Exception { + doWithResource(stepHarness, "objectWithNull"); + } + + @Test + public void handlesInvalidJson() { + assertThatThrownBy(() -> doWithResource(stepHarness, "invalidJson")).isInstanceOf(AssertionError.class).hasMessage("Invalid JSON file provided"); + } + + @Test + public void handlesNotJson() { + assertThatThrownBy(() -> doWithResource(stepHarness, "notJson")).isInstanceOf(AssertionError.class).hasMessage("Invalid JSON file provided"); + } + + @Test + public void canSetCustomIndentationLevel() throws Exception { + FormatterStep step = JsonSimpleStep.create(6, TestProvisioner.mavenCentral()); + StepHarness stepHarness = StepHarness.forStep(step); + + String before = "json/singletonArrayBefore.json"; + String after = "json/singletonArrayAfter6Spaces.json"; + stepHarness.testResource(before, after); + } + + @Test + public void canSetIndentationLevelTo0() throws Exception { + FormatterStep step = JsonSimpleStep.create(0, TestProvisioner.mavenCentral()); + StepHarness stepHarness = StepHarness.forStep(step); + + String before = "json/singletonArrayBefore.json"; + String after = "json/singletonArrayAfter0Spaces.json"; + stepHarness.testResource(before, after); + } + + @Test + public void equality() { + new SerializableEqualityTester() { + int spaces = 0; + + @Override + protected void setupTest(API api) { + // no changes, are the same + api.areDifferentThan(); + + // with different spacing + spaces = 1; + api.areDifferentThan(); + } + + @Override + protected FormatterStep create() { + return JsonSimpleStep.create(spaces, TestProvisioner.mavenCentral()); + } + }.testEquals(); + } + + private static void doWithResource(StepHarness stepHarness, String name) throws Exception { + String before = String.format("json/%sBefore.json", name); + String after = String.format("json/%sAfter.json", name); + stepHarness.testResource(before, after); + } +}