Compare commits

...

20 Commits

Author SHA1 Message Date
Lucien Greathouse
5a0a8f5077 Release v7.0.0-alpha.4 2021-05-14 16:37:49 -04:00
Lucien Greathouse
1c281539e0 Update rbx_dom_lua, using subfolder now 2021-05-14 16:36:32 -04:00
Lucien Greathouse
ef41d14f50 Update changelog 2021-05-14 16:29:05 -04:00
Lucien Greathouse
aa29397732 Update changelog 2021-05-14 16:18:54 -04:00
Mixu78
ca8865a3ce Do not validate .model.json files with no content/whitespace only (#420)
* Ignore empty/whitespace-only model.json files

* Ignore no return value from model.json files during snapshot

* Use str::from_utf8 instead of String::from_utf8

* Revert "Ignore no return value from model.json files during snapshot"

This reverts commit 0aef16e30a.

* Add test for empty .model.json files

* Change empty .model.json check method

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>

* Format code with cargo fmt

* Use raw string instead

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-05-14 16:16:05 -04:00
Lucien Greathouse
7599ef6626 Improve error messages 2021-05-14 15:58:18 -04:00
Lucien Greathouse
43e71f9242 Upgrade Rojo to 6.1.0 (lol) 2021-05-14 15:09:19 -04:00
Lucien Greathouse
aac9b25efe Update dependencies 2021-05-14 15:08:48 -04:00
Lucien Greathouse
532d170585 Fix 'Open Scripts Externally' crashing studio.
Closes #369.
2021-04-23 16:59:59 -04:00
Lucien Greathouse
3dcb14013b Update changelog 2021-04-23 15:45:10 -04:00
Lucien Greathouse
a4c782cd35 Mark two-way sync as experimental in UI 2021-04-23 15:41:17 -04:00
Mixu78
0779baa0ac Block usage of "Name" or "Parent" in $properties (#413)
* Ignore usage of "Name" or "Parent" in $properties

* Use match instead of array

* Add changelog entry

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-04-23 15:11:17 -04:00
Lucien Greathouse
0849fab644 Commit snapshots from project file change 2021-04-23 14:34:41 -04:00
Mixu78
d85bca2e7e Reword top level model error message (#412) 2021-04-16 12:39:30 -04:00
Lucien Greathouse
c7ab6c435c Add gameId and placeId project properties 2021-04-15 14:27:35 -04:00
Lucien Greathouse
98db3b4f08 Release 7.0.0-alpha.3 2021-04-09 18:06:54 -04:00
MSAA
0e7ba839ed change server bind address (#403)
* web/mod.rs - change server bind address

127.0.0.1 is a loopback interface, and only works on the same host
0.0.0.0 will allow connections from other hosts

ideally, this should be a console arg - but it's a quick fix

* implement --address option, revert default bind address to 127.0.0.1

* revert silly autoformatting

* ok, actually using rustfmt now

* More precise --address flag description

* Use SocketAddr where available, take advantage of const-ness

* Display 'localhost' if address is loopback

* Update Changelog

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-03-18 22:41:31 -04:00
Lucien Greathouse
2c27691e57 Update dependencies 2021-03-08 22:01:38 -05:00
Lucien Greathouse
934506bdfd Update to latest rbx_binary 2021-03-04 15:11:44 -07:00
Lucien Greathouse
f313fa4ae1 Update dependencies 2021-03-04 12:55:49 -07:00
50 changed files with 2715 additions and 813 deletions

View File

@@ -2,6 +2,30 @@
## Unreleased Changes
## [7.0.0-alpha.4][7.0.0-alpha.4] (May 5, 2021)
* Added the `gameId` and `placeId` optional properties to project files.
* When connecting from the Rojo Roblox Studio plugin, Rojo will set the game and place ID of the current place to these values, if set.
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
* Empty `.model.json` files will no longer cause errors. ([#420][pr-420])
* When specifying `$path` on a service, Rojo now keeps the correct class name. ([#331][issue-331])
* Improved error messages for misconfigured projects.
[issue-331]: https://github.com/rojo-rbx/rojo/issues/331
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
[pr-420]: https://github.com/rojo-rbx/rojo/pull/420
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
[7.0.0-alpha.4]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.4
## [7.0.0-alpha.3][7.0.0-alpha.3] (February 19, 2021)
* Updated dependencies, fixing `OptionalCoordinateFrame`-related issues.
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
[pr-403]: https://github.com/rojo-rbx/rojo/pull/403
[7.0.0-alpha.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.3
## [7.0.0-alpha.2][7.0.0-alpha.2] (February 19, 2021)
* Fixed incorrect protocol version between the client and server.

427
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "rojo"
version = "7.0.0-alpha.2"
version = "7.0.0-alpha.4"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers"
license = "MPL-2.0"

View File

@@ -1,3 +1,3 @@
[tools]
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
rojo = { source = "rojo-rbx/rojo", version = "6.1.0" }
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }

View File

@@ -1,44 +0,0 @@
stds.roblox = {
read_globals = {
game = {
other_fields = true,
},
-- Roblox globals
"script",
-- Extra functions
"tick", "warn",
"wait", "typeof",
-- Types
"CFrame",
"Color3",
"Enum",
"Instance",
"NumberRange",
"Rect",
"UDim", "UDim2",
"Vector2", "Vector3",
"Vector2int16", "Vector3int16",
}
}
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}
}
ignore = {
"212", -- unused arguments
}
std = "lua51+roblox"
files["**/*.spec.lua"] = {
std = "+testez",
}

View File

@@ -1,2 +0,0 @@
# rbx_dom_lua
Roblox Lua implementation of rbx-dom mechanisms, intended to work with rbx_dom_weak and friends.

View File

@@ -1 +0,0 @@
require(game.ReplicatedStorage.TestEZ).TestBootstrap:run({game.ReplicatedStorage.RbxDom})

View File

@@ -1,4 +0,0 @@
#!/bin/sh
rojo build test-place.project.json -o TestPlace.rbxlx
run-in-roblox --script run-tests.lua --place TestPlace.rbxlx

View File

@@ -1,35 +0,0 @@
{
"name": "rbx_dom_lua test place",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"RbxDom": {
"$path": "src"
},
"TestEZ": {
"$path": "modules/testez/lib"
}
},
"ServerScriptService": {
"$className": "ServerScriptService",
"Run Tests": {
"$path": "run-tests.lua"
}
},
"Players": {
"$className": "Players",
"$properties": {
"CharacterAutoLoads": false
}
},
"HttpService": {
"$className": "HttpService",
"$properties": {
"HttpEnabled": true
}
}
}
}

View File

@@ -205,7 +205,7 @@ function SettingsPage:render()
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "Editing files in Studio will sync them into the filesystem",
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
transparency = self.props.transparency,
layoutOrder = 2,
}),

View File

@@ -5,7 +5,7 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
return strict("Config", {
isDevBuild = isDevBuild,
codename = "Epiphany",
version = {7, 0, 0, "-alpha.2"},
version = {7, 0, 0, "-alpha.4"},
expectedServerVersionString = "7.0 or newer",
protocolVersion = 4,
defaultHost = "localhost",

View File

@@ -1,4 +1,5 @@
local StudioService = game:GetService("StudioService")
local RunService = game:GetService("RunService")
local Log = require(script.Parent.Parent.Log)
local Fmt = require(script.Parent.Parent.Fmt)
@@ -111,6 +112,7 @@ function ServeSession:start()
self.__apiContext:connect()
:andThen(function(serverInfo)
self:__setStatus(Status.Connected, serverInfo.projectName)
self:__applyGameAndPlaceId(serverInfo)
local rootInstanceId = serverInfo.rootInstanceId
@@ -128,6 +130,16 @@ function ServeSession:stop()
self:__stopInternal()
end
function ServeSession:__applyGameAndPlaceId(serverInfo)
if serverInfo.gameId ~= nil then
game:SetUniverseId(serverInfo.gameId)
end
if serverInfo.placeId ~= nil then
game:SetPlaceId(serverInfo.placeId)
end
end
function ServeSession:__onActiveScriptChanged(activeScript)
if not self.__openScriptsExternally then
Log.trace("Not opening script {} because feature not enabled.", activeScript)
@@ -150,10 +162,18 @@ function ServeSession:__onActiveScriptChanged(activeScript)
Log.debug("Trying to open script {} externally...", activeScript)
-- Force-close the script inside Studio
local existingParent = activeScript.Parent
activeScript.Parent = nil
activeScript.Parent = existingParent
-- Force-close the script inside Studio... with a small delay in the middle
-- to prevent Studio from crashing.
spawn(function()
local existingParent = activeScript.Parent
activeScript.Parent = nil
for i = 1, 3 do
RunService.Heartbeat:Wait()
end
activeScript.Parent = existingParent
end)
-- Notify the Rojo server to open this script
self.__apiContext:open(scriptId)

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: add_folder
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: edit_init
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: empty
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -0,0 +1,28 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
---
instances:
id-2:
Children:
- id-3
ClassName: Folder
Id: id-2
Metadata:
ignoreUnknownInstances: false
Name: empty_folder
Parent: "00000000000000000000000000000000"
Properties: {}
id-3:
Children: []
ClassName: Model
Id: id-3
Metadata:
ignoreUnknownInstances: false
Name: test
Parent: id-2
Properties: {}
messageCursor: 1
sessionId: id-1

View File

@@ -0,0 +1,18 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
---
instances:
id-2:
Children: []
ClassName: Folder
Id: id-2
Metadata:
ignoreUnknownInstances: false
Name: empty_folder
Parent: "00000000000000000000000000000000"
Properties: {}
messageCursor: 0
sessionId: id-1

View File

@@ -0,0 +1,14 @@
---
source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: empty_folder
protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
sessionId: id-1

View File

@@ -0,0 +1,21 @@
---
source: tests/tests/serve.rs
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
---
messageCursor: 1
messages:
- added:
id-3:
Children: []
ClassName: Model
Id: id-3
Metadata:
ignoreUnknownInstances: false
Name: test
Parent: id-2
Properties: {}
removed: []
updated: []
sessionId: id-1

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: move_folder_of_stuff
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: remove_file
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -4,6 +4,8 @@ expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: scripts
protocolVersion: 4
rootInstanceId: id-2

View File

@@ -1,5 +1,5 @@
{
"name": "rbx_dom_lua",
"name": "empty_folder",
"tree": {
"$path": "src"
}

View File

@@ -12,6 +12,7 @@ use std::{
env,
error::Error,
fmt,
net::IpAddr,
path::{Path, PathBuf},
str::FromStr,
};
@@ -186,7 +187,11 @@ pub struct ServeCommand {
#[structopt(default_value = "")]
pub project: PathBuf,
/// The port to listen on. Defaults to the project's preference, or 34872 if
/// The IP address to listen on. Defaults to `127.0.0.1`.
#[structopt(long)]
pub address: Option<IpAddr>,
/// The port to listen on. Defaults to the project's preference, or `34872` if
/// it has none.
#[structopt(long)]
pub port: Option<u16>,

View File

@@ -1,5 +1,7 @@
use std::{
io::{self, Write},
net::IpAddr,
net::Ipv4Addr,
sync::Arc,
};
@@ -13,6 +15,7 @@ use crate::{
web::LiveServer,
};
const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const DEFAULT_PORT: u16 = 34872;
pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
@@ -20,6 +23,8 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let session = Arc::new(ServeSession::new(vfs, &options.absolute_project())?);
let ip = options.address.unwrap_or(DEFAULT_BIND_ADDRESS.into());
let port = options
.port
.or_else(|| session.project_port())
@@ -27,13 +32,13 @@ pub fn serve(global: GlobalOptions, options: ServeCommand) -> Result<()> {
let server = LiveServer::new(session);
let _ = show_start_message(port, global.color.into());
server.start(port);
let _ = show_start_message(ip, port, global.color.into());
server.start((ip, port).into());
Ok(())
}
fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> {
fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io::Result<()> {
let writer = BufferWriter::stdout(color);
let mut buffer = writer.buffer();
@@ -41,7 +46,12 @@ fn show_start_message(port: u16, color: ColorChoice) -> io::Result<()> {
write!(&mut buffer, " Address: ")?;
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(&mut buffer, "localhost")?;
if bind_address.is_loopback() {
writeln!(&mut buffer, "localhost")?;
} else {
writeln!(&mut buffer, "{}", bind_address)?;
}
buffer.set_color(&ColorSpec::new())?;
write!(&mut buffer, " Port: ")?;

View File

@@ -57,6 +57,14 @@ pub struct Project {
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_place_ids: Option<HashSet<u64>>,
/// If specified, sets the current place's place ID when connecting to the
/// Rojo server from Roblox Studio.
pub place_id: Option<u64>,
/// If specified, sets the current place's game ID when connecting to the
/// Rojo server from Roblox Studio.
pub game_id: Option<u64>,
/// A list of globs, relative to the folder the project file is in, that
/// match files that should be excluded if Rojo encounters them.
#[serde(default, skip_serializing_if = "Vec::is_empty")]

View File

@@ -195,6 +195,14 @@ impl ServeSession {
self.root_project.serve_port
}
pub fn place_id(&self) -> Option<u64> {
self.root_project.place_id
}
pub fn game_id(&self) -> Option<u64> {
self.root_project.game_id
}
pub fn start_time(&self) -> Instant {
self.start_time
}

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use std::{borrow::Cow, collections::HashMap, path::Path, str};
use anyhow::Context;
use memofs::Vfs;
@@ -18,7 +18,14 @@ pub fn snapshot_json_model(
instance_name: &str,
) -> SnapshotInstanceResult {
let contents = vfs.read(path)?;
let instance: JsonModel = serde_json::from_slice(&contents)
let contents_str = str::from_utf8(&contents)
.with_context(|| format!("File was not valid UTF-8: {}", path.display()))?;
if contents_str.trim().is_empty() {
return Ok(None);
}
let instance: JsonModel = serde_json::from_str(contents_str)
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
let mut snapshot = instance

View File

@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashMap, path::Path};
use anyhow::Context;
use anyhow::{bail, Context};
use memofs::Vfs;
use rbx_reflection::ClassTag;
@@ -66,11 +66,13 @@ pub fn snapshot_project_node(
) -> SnapshotInstanceResult {
let project_folder = project_path.parent().unwrap();
let name = Cow::Owned(instance_name.to_owned());
let mut class_name = node
let class_name_from_project = node
.class_name
.as_ref()
.map(|name| Cow::Owned(name.clone()));
let mut class_name_from_path = None;
let name = Cow::Owned(instance_name.to_owned());
let mut properties = HashMap::new();
let mut children = Vec::new();
let mut metadata = InstanceMetadata::default();
@@ -85,24 +87,7 @@ pub fn snapshot_project_node(
};
if let Some(snapshot) = snapshot_from_vfs(context, vfs, &path)? {
// If a class name was already specified, then it'll override the
// class name of this snapshot ONLY if it's a Folder.
//
// This restriction is in place to prevent applying properties to
// instances that don't make sense. The primary use-case for using
// $className and $path at the same time is to use a directory as a
// service in a place file.
class_name = match class_name {
Some(class_name) => {
if snapshot.class_name == "Folder" {
Some(class_name)
} else {
// TODO: Turn this into an error object.
panic!("If $className and $path are specified, $path must yield an instance of class Folder");
}
}
None => Some(snapshot.class_name),
};
class_name_from_path = Some(snapshot.class_name);
// Properties from the snapshot are pulled in unchanged, and
// overridden by properties set on the project node.
@@ -129,34 +114,66 @@ pub fn snapshot_project_node(
}
}
let class_name = class_name
.or_else(|| {
// If className wasn't defined from another source, we may be able
// to infer one.
let class_name_from_inference = infer_class_name(&name, parent_class);
let parent_class = parent_class?;
let class_name = match (
class_name_from_project,
class_name_from_path,
class_name_from_inference,
) {
// These are the easy, happy paths!
(Some(project), None, None) => project,
(None, Some(path), None) => path,
(None, None, Some(inference)) => inference,
if parent_class == "DataModel" {
// Members of DataModel with names that match known services are
// probably supposed to be those services.
// If the user specifies a class name, but there's an inferred class
// name, we prefer the name listed explicitly by the user.
(Some(project), None, Some(_)) => project,
let descriptor = rbx_reflection_database::get().classes.get(&name)?;
if descriptor.tags.contains(&ClassTag::Service) {
return Some(name.clone());
}
} else if parent_class == "StarterPlayer" {
// StarterPlayer has two special members with their own classes.
if name == "StarterPlayerScripts" || name == "StarterCharacterScripts" {
return Some(name.clone());
}
// If the user has a $path pointing to a folder and we're able to infer
// a class name, let's use the inferred name. If the path we're pointing
// to isn't a folder, though, that's a user error.
(None, Some(path), Some(inference)) => {
if path == "Folder" {
inference
} else {
path
}
}
None
})
// TODO: Turn this into an error object.
.expect("$className or $path must be specified");
(Some(project), Some(path), _) => {
if path == "Folder" {
project
} else {
bail!(
"ClassName for Instance \"{}\" was specified in both the project file (as \"{}\") and from the filesystem (as \"{}\").\n\
If $className and $path are both set, $path must refer to a Folder.
\n\
Project path: {}\n\
Filesystem path: {}\n",
instance_name,
project,
path,
project_path.display(),
node.path.as_ref().unwrap().display()
);
}
}
(None, None, None) => {
bail!(
"Instance \"{}\" is missing some required information.\n\
One of the following must be true:\n\
- $className must be set to the name of a Roblox class\n\
- $path must be set to a path of an instance\n\
- The instance must be a known service, like ReplicatedStorage\n\
\n\
Project path: {}",
instance_name,
project_path.display(),
);
}
};
for (child_name, child_project_node) in &node.children {
if let Some(child) = snapshot_project_node(
@@ -182,6 +199,20 @@ pub fn snapshot_project_node(
)
})?;
match key.as_str() {
"Name" | "Parent" => {
log::warn!(
"Property '{}' cannot be set manually, ignoring. Attempted to set in '{}' at {}",
key,
instance_name,
project_path.display()
);
continue;
}
_ => {}
}
properties.insert(key.clone(), value);
}
@@ -216,6 +247,32 @@ pub fn snapshot_project_node(
}))
}
fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Cow<'static, str>> {
// If className wasn't defined from another source, we may be able
// to infer one.
let parent_class = parent_class?;
if parent_class == "DataModel" {
// Members of DataModel with names that match known services are
// probably supposed to be those services.
let descriptor = rbx_reflection_database::get().classes.get(name)?;
if descriptor.tags.contains(&ClassTag::Service) {
return Some(Cow::Owned(name.to_owned()));
}
} else if parent_class == "StarterPlayer" {
// StarterPlayer has two special members with their own classes.
if name == "StarterPlayerScripts" || name == "StarterCharacterScripts" {
return Some(Cow::Owned(name.to_owned()));
}
}
None
}
// #[cfg(feature = "broken-tests")]
#[cfg(test)]
mod test {

View File

@@ -32,7 +32,7 @@ pub fn snapshot_rbxm(
Ok(Some(snapshot))
} else {
anyhow::bail!(
"Rojo doesn't have support for model files with zero or more than one top-level instances yet.\n\n \
"Rojo currently only supports model files with one top-level instance.\n\n \
Check the model file at path {}",
path.display()
);

View File

@@ -35,7 +35,7 @@ pub fn snapshot_rbxmx(
Ok(Some(snapshot))
} else {
anyhow::bail!(
"Rojo doesn't have support for model files with zero or more than one top-level instances yet.\n\n \
"Rojo currently only supports model files with one top-level instance.\n\n \
Check the model file at path {}",
path.display()
);

View File

@@ -69,6 +69,8 @@ impl ApiService {
session_id: self.serve_session.session_id(),
project_name: self.serve_session.project_name().to_owned(),
expected_place_ids: self.serve_session.serve_place_ids().cloned(),
place_id: self.serve_session.place_id(),
game_id: self.serve_session.game_id(),
root_instance_id,
})
}

View File

@@ -104,6 +104,8 @@ pub struct ServerInfoResponse {
pub protocol_version: u64,
pub project_name: String,
pub expected_place_ids: Option<HashSet<u64>>,
pub game_id: Option<u64>,
pub place_id: Option<u64>,
pub root_instance_id: Ref,
}

View File

@@ -4,7 +4,7 @@ pub mod interface;
mod ui;
mod util;
use std::sync::Arc;
use std::{net::SocketAddr, sync::Arc};
use futures::{
future::{self, FutureResult},
@@ -57,9 +57,7 @@ impl LiveServer {
LiveServer { serve_session }
}
pub fn start(self, port: u16) {
let address = ([127, 0, 0, 1], port).into();
pub fn start(self, address: SocketAddr) {
let server = Server::bind(&address)
.serve(move || {
let service: FutureResult<_, hyper::Error> =

View File

@@ -0,0 +1,7 @@
{
"name": "bad_classname_path_conflict",
"tree": {
"$className": "DataModel",
"$path": "foo.txt"
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "bad_no_classname",
"tree": {}
}

View File

@@ -186,3 +186,37 @@ fn move_folder_of_stuff() {
);
});
}
#[test]
fn empty_json_model() {
run_serve_test("empty_json_model", |session, mut redactions| {
let info = session.get_api_rojo().unwrap();
let root_id = info.root_instance_id;
assert_yaml_snapshot!("empty_json_model_info", redactions.redacted_yaml(info));
let read_response = session.get_api_read(root_id).unwrap();
assert_yaml_snapshot!(
"empty_json_model_all",
read_response.intern_and_redact(&mut redactions, root_id)
);
fs::write(
session.path().join("src/test.model.json"),
r#"{"ClassName": "Model"}"#,
)
.unwrap();
let subscribe_response = session.get_api_subscribe(0).unwrap();
assert_yaml_snapshot!(
"empty_json_model_subscribe",
subscribe_response.intern_and_redact(&mut redactions, ())
);
let read_response = session.get_api_read(root_id).unwrap();
assert_yaml_snapshot!(
"empty_json_model_all-2",
read_response.intern_and_redact(&mut redactions, root_id)
);
});
}