Skip to content

Schema Tests #970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ sourceSets{
srcDirs("src")
}
}
test{
kotlin{
srcDirs("test")
}
}
}

compose.desktop {
Expand Down Expand Up @@ -99,6 +104,17 @@ dependencies {

implementation(libs.compottie)
implementation(libs.kaml)

testImplementation(kotlin("test"))
testImplementation(libs.mockitoKotlin)
testImplementation(libs.junitJupiter)
testImplementation(libs.junitJupiterParams)
}

tasks.test {
useJUnitPlatform()
workingDir = file("build/test")
workingDir.mkdirs()
}

tasks.compileJava{
Expand Down
8 changes: 4 additions & 4 deletions app/src/processing/app/Base.java
Original file line number Diff line number Diff line change
Expand Up @@ -1364,10 +1364,10 @@ private File moveLikeSketchFolder(File pdeFile, String baseName) throws IOExcept
* @param schemeUri the full URI, including pde://
*/
public Editor handleScheme(String schemeUri) {
// var result = Schema.handleSchema(schemeUri, this);
// if (result != null) {
// return result;
// }
var result = Schema.handleSchema(schemeUri, this);
if (result != null) {
return result;
}

String location = schemeUri.substring(6);
if (location.length() > 0) {
Expand Down
54 changes: 26 additions & 28 deletions app/src/processing/app/Schema.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ class Schema {
private fun handleSketchUrl(uri: URI): Editor?{
val url = File(uri.path.replace("/url/", ""))

val tempSketchFolder = File(Base.untitledFolder, url.nameWithoutExtension)
val rand = (1..6)
.map { (('a'..'z') + ('A'..'Z')).random() }
.joinToString("")

val tempSketchFolder = File(File(Base.untitledFolder, rand), url.nameWithoutExtension)
tempSketchFolder.mkdirs()
val tempSketchFile = File(tempSketchFolder, "${tempSketchFolder.name}.pde")

Expand All @@ -71,7 +75,7 @@ class Schema {
?.map { it.split("=") }
?.associate {
URLDecoder.decode(it[0], StandardCharsets.UTF_8) to
URLDecoder.decode(it[1], StandardCharsets.UTF_8)
URLDecoder.decode(it[1], StandardCharsets.UTF_8)
}
?: emptyMap()
options["data"]?.let{ data ->
Expand All @@ -81,15 +85,15 @@ class Schema {
downloadFiles(uri, code, File(sketchFolder, "code"))
}
options["pde"]?.let{ pde ->
downloadFiles(uri, pde, sketchFolder)
downloadFiles(uri, pde, sketchFolder, "pde")
}
options["mode"]?.let{ mode ->
val modeFile = File(sketchFolder, "sketch.properties")
modeFile.writeText("mode.id=$mode")
}

}
private fun downloadFiles(uri: URI, urlList: String, targetFolder: File){
private fun downloadFiles(uri: URI, urlList: String, targetFolder: File, extension: String = ""){
Thread{
targetFolder.mkdirs()

Expand All @@ -101,37 +105,31 @@ class Schema {
val files = urlList.split(",")

files.filter { it.isNotBlank() }
.map{ it.split(":", limit = 2) }
.map{ segments ->
if(segments.size == 2){
if(segments[0].isBlank()){
return@map listOf(null, segments[1])
}
return@map segments
}
return@map listOf(null, segments[0])
.map {
if (it.contains(":")) it
else "$it:$it"
}
.map{ it.split(":", limit = 2) }
.forEach { (name, content) ->
var target = File(targetFolder, name)
if(extension.isNotBlank() && target.extension != extension){
target = File(targetFolder, "$name.$extension")
}
try{
// Try to decode the content as base64
val file = Base64.getDecoder().decode(content)
if(name == null){
if(name.isBlank()){
Messages.err("Base64 files needs to start with a file name followed by a colon")
return@forEach
}
File(targetFolder, name).writeBytes(file)
target.writeBytes(file)
}catch(_: IllegalArgumentException){
// Assume it's a URL and download it
var url = URI.create(content)
if(url.host == null){
url = URI.create("https://$base/$content")
}
if(url.scheme == null){
url = URI.create("https://$content")
}

val target = File(targetFolder, name ?: url.path.split("/").last())
url.toURL().openStream().use { input ->
val url = URL(when{
content.startsWith("https://") -> content
content.startsWith("http://") -> content.replace("http://", "https://")
URL("https://$content").path.isNotBlank() -> "https://$content"
else -> "https://$base/$content"
})
url.openStream().use { input ->
target.outputStream().use { output ->
input.copyTo(output)
}
Expand All @@ -148,7 +146,7 @@ class Schema {
?.map { it.split("=") }
?.associate {
URLDecoder.decode(it[0], StandardCharsets.UTF_8) to
URLDecoder.decode(it[1], StandardCharsets.UTF_8)
URLDecoder.decode(it[1], StandardCharsets.UTF_8)
}
?: emptyMap()
for ((key, value) in options){
Expand Down
113 changes: 113 additions & 0 deletions app/test/processing/app/SchemaTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package processing.app

import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.ArgumentCaptor
import org.mockito.MockedStatic
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import java.io.File
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.test.Test


class SchemaTest {
private val base: Base = mock<Base>{

}
companion object {
val preferences: MockedStatic<Preferences> = mockStatic(Preferences::class.java)
}


@Test
fun testLocalFiles() {
val file = "/this/is/a/local/file"
Schema.handleSchema("pde://$file", base)
verify(base).handleOpen(file)
}

@Test
fun testNewSketch() {
Schema.handleSchema("pde://sketch/new", base)
verify(base).handleNew()
}

@OptIn(ExperimentalEncodingApi::class)
@Test
fun testBase64SketchAndExtraFiles() {
val sketch = """
void setup(){

}
void draw(){

}
""".trimIndent()

val base64 = Base64.encode(sketch.toByteArray())
Schema.handleSchema("pde://sketch/base64/$base64?pde=AnotherFile:$base64", base)
val captor = ArgumentCaptor.forClass(String::class.java)

verify(base).handleOpenUntitled(captor.capture())

val file = File(captor.value)
assert(file.exists())
assert(file.readText() == sketch)

val extra = file.parentFile.resolve("AnotherFile.pde")
assert(extra.exists())
assert(extra.readText() == sketch)
file.parentFile.deleteRecursively()
}

@Test
fun testURLSketch() {
Schema.handleSchema("pde://sketch/url/github.com/processing/processing-examples/raw/refs/heads/main/Basics/Arrays/Array/Array.pde", base)

val captor = ArgumentCaptor.forClass(String::class.java)
verify(base).handleOpenUntitled(captor.capture())
val output = File(captor.value)
assert(output.exists())
assert(output.name == "Array.pde")
assert(output.extension == "pde")
assert(output.parentFile.name == "Array")

output.parentFile.parentFile.deleteRecursively()
}

@ParameterizedTest
@ValueSource(strings = [
"Module.pde:https://github.com/processing/processing-examples/raw/refs/heads/main/Basics/Arrays/ArrayObjects/Module.pde",
"Module.pde",
"Module:Module.pde",
"Module:https://github.com/processing/processing-examples/raw/refs/heads/main/Basics/Arrays/ArrayObjects/Module.pde",
"Module.pde:github.com/processing/processing-examples/raw/refs/heads/main/Basics/Arrays/ArrayObjects/Module.pde"
])
fun testURLSketchWithFile(file: String){
Schema.handleSchema("pde://sketch/url/github.com/processing/processing-examples/raw/refs/heads/main/Basics/Arrays/ArrayObjects/ArrayObjects.pde?pde=$file", base)

val captor = ArgumentCaptor.forClass(String::class.java)
verify(base).handleOpenUntitled(captor.capture())

// wait for threads to resolve
Thread.sleep(1000)

val output = File(captor.value)
assert(output.parentFile.name == "ArrayObjects")
assert(output.exists())
assert(output.parentFile.resolve("Module.pde").exists())
output.parentFile.parentFile.deleteRecursively()
}

@Test
fun testPreferences() {
Schema.handleSchema("pde://preferences?test=value", base)
preferences.verify {
Preferences.set("test", "value")
Preferences.save()
}
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
kotlin = "2.0.20"
compose-plugin = "1.7.1"
jogl = "2.5.0"
jupiter = "5.12.0"

[libraries]
jogl = { module = "org.jogamp.jogl:jogl-all-main", version.ref = "jogl" }
Expand All @@ -12,7 +13,10 @@ jnaplatform = { module = "net.java.dev.jna:jna-platform", version = "5.12.1" }
compottie = { module = "io.github.alexzhirkevich:compottie", version = "2.0.0-rc02" }
kaml = { module = "com.charleskorn.kaml:kaml", version = "0.65.0" }
junit = { module = "junit:junit", version = "4.13.2" }
junitJupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jupiter" }
junitJupiterParams = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "jupiter" }
mockito = { module = "org.mockito:mockito-core", version = "4.11.0" }
mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.4.0" }
antlr = { module = "org.antlr:antlr4", version = "4.7.2" }
eclipseJDT = { module = "org.eclipse.jdt:org.eclipse.jdt.core", version = "3.16.0" }
eclipseJDTCompiler = { module = "org.eclipse.jdt:org.eclipse.jdt.compiler.apt", version = "1.3.400" }
Expand Down
Loading