blob: 0bc97336aa400218f2341bf5ef408231fffd520d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
use super::*;
use maka_packages::Package;
use maka_ui_runtime::Surface;
use std::{path::Path, process::Command};
#[test]
#[ignore = "opt-in packaged product build and actual native schema compiler"]
fn packaged_product_component_schemas_compile_independently_for_terminal_presentation() {
use maka_plugin_core::{
contract::Interface,
package::{Declaration, Implementation},
};
let timer = timer();
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let build = Command::new("node")
.arg("products/maka/bundle/scripts/build.mjs")
.current_dir(&root)
.output()
.unwrap();
assert!(
build.status.success(),
"{}",
String::from_utf8_lossy(&build.stderr)
);
let dist = root.join("products/maka/bundle/dist");
let names: Vec<String> =
serde_json::from_slice(&std::fs::read(dist.join("packages.json")).unwrap()).unwrap();
let mut checked_palette = 0;
for name in names {
let manifest: Value = serde_json::from_slice(
&std::fs::read(
dist.join("packages")
.join(&name)
.join("maka.extension.json"),
)
.unwrap(),
)
.unwrap();
let declaration: Declaration =
serde_json::from_value(manifest["application"].clone()).unwrap();
for (target, entry) in declaration.entries {
if name == "command-palette" {
let Implementation::JavaScript { module, .. } = &entry.implementation else {
panic!("ordinary palette requires JavaScript");
};
let mut realm = maka_js_runtime::plugin::Foreground::new(
Default::default(),
timer.handle().clone(),
)
.unwrap();
let palette = realm
.load(
"palette",
&std::fs::read_to_string(
dist.join("packages/command-palette").join(module),
)
.unwrap(),
)
.unwrap();
let actual: BTreeMap<String, maka_ui_runtime::abi::UiExport> =
serde_json::from_value(
realm
.call(&palette, &["activate".into()], &[json!({}), json!({})])
.unwrap(),
)
.unwrap();
let maka_ui_runtime::abi::UiExport::Component {
contract,
component,
} = &actual["CommandPalette"]
else {
panic!("palette must publish its component");
};
assert_eq!(contract, &entry.exports["CommandPalette"].contract);
let callbacks = realm
.capture(&palette, component, &["mount", "event", "update"])
.unwrap();
let appearance = realm
.load(
"appearance",
&std::fs::read_to_string(dist.join("packages/appearance").join(module))
.unwrap(),
)
.unwrap();
let theme = realm
.call(
&appearance,
&["activate".into()],
&[json!({"scheme":"light"})],
)
.unwrap()["Appearance"]["value"]
.clone();
let props = json!({"theme":theme, "locale":"en", "open":true, "busy":false,
"commands":[{"id":"settings", "label":"Settings", "hint":"", "disabled":false}]});
let mounted: maka_ui_runtime::ComponentOutput = serde_json::from_value(
realm
.call_bound(&callbacks, "mount", &[&props, &json!({})])
.unwrap(),
)
.unwrap();
assert!(mounted.actions.is_empty());
assert!(mounted.events.is_empty());
let closed: maka_ui_runtime::ComponentOutput = serde_json::from_value(
realm
.call_bound(
&callbacks,
"event",
&[
&mounted.frame.state,
&json!({"kind":"raw", "key":"close", "event":null}),
&props,
&json!({}),
],
)
.unwrap(),
)
.unwrap();
assert_eq!(closed.events[0].name, "closed");
assert_eq!(
closed.frame.state["open"], true,
"parent still owns controlled open"
);
realm.close(&palette).unwrap();
realm.close(&appearance).unwrap();
}
for (export, contract) in entry.exports {
let Interface::Component {
props,
events,
captures,
} = contract.contract.interface
else {
continue;
};
let check = |schema: &Value| {
jsonschema::validator_for(schema)
.unwrap_or_else(|error| panic!("{name}/{target:?}/{export}: {error}"))
};
check(&props);
for schema in events.values() {
check(schema);
}
for capture in captures.values() {
check(&capture.input);
}
if export == "CommandPalette" {
checked_palette += 1;
assert!(check(&events["selected"]).is_valid(&json!({
"id":"session", "title":"Native schema checked", "revision":1,
"updatedAt":1, "archived":false, "activity":"idle", "labels":[],
"workspace":{"path":"/workspace", "directoryIdentity":format!("sha256:{}", "a".repeat(64)), "identity":"workspace:v1:00000000-0000-0000-0000-000000000001"},
})));
assert!(!check(&events["selected"]).is_valid(&json!({"id":"session"})));
}
}
}
}
assert_eq!(checked_palette, 1);
}
#[test]
fn sdk_typescript_bundle_executes_independent_terminal_mounts() {
let timer = timer();
let work = tempfile::tempdir().unwrap();
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let sdk = root.join("packages/plugin-sdk");
let status = Command::new("node")
.arg(sdk.join("scripts/bundle.mjs"))
.arg(sdk.join("examples/counter/terminal.ts"))
.arg(work.path().join("index.mjs"))
.current_dir(&root)
.status()
.unwrap();
assert!(status.success(), "SDK terminal bundle failed");
let (mut input, mut packages) = support::fixture("");
let old = packages.pop().unwrap();
let files = BTreeMap::from([
(
maka_packages::MANIFEST_FILE.into(),
serde_json::to_vec(&json!({"schemaVersion":1,"id":old.manifest().id,
"application":old.manifest().application}))
.unwrap(),
),
(
"index.mjs".into(),
std::fs::read(work.path().join("index.mjs")).unwrap(),
),
]);
let package = Package::new(files).unwrap();
input.instances.get_mut("app").unwrap().package = package.application_ref().into();
packages.push(package);
let mut app = Application::new(
&input,
packages,
timer.handle().clone(),
maka_ui_js::execution(maka_js_runtime::plugin::Limits {
synchronous_slice: Duration::from_millis(100),
..Default::default()
}),
crate::support::scene::registry::<()>(PresentationTarget::Tui),
Default::default(),
)
.unwrap();
let first = app.mount_launch(json!({"count":10})).unwrap();
let second = app.mount_launch(json!({"count":20})).unwrap();
assert_eq!(
app.frame(&first).unwrap().surface,
Some(Surface::Terminal {
binding: "surface".into()
})
);
app.dispatch(&first, 0, "inc", json!({})).unwrap();
assert_eq!(app.frame(&first).unwrap().state["count"], 11);
assert_eq!(app.frame(&second).unwrap().state["count"], 20);
assert!(app.dispatch(&first, 0, "inc", json!({})).is_err());
app.close(&first, Ok(())).unwrap();
app.close(&second, Ok(())).unwrap();
app.shutdown().unwrap();
}