Compare commits

...

12 Commits

Author SHA1 Message Date
a3ff0a45eb Disable build-test-web's tests on release or nightly build (#3296) 2024-08-06 05:50:49 -04:00
4617ad0fed Cut release v0.24.9 (#3295) 2024-08-06 05:30:34 -04:00
5fa51a2f92 Revert "Cut release v0.24.9"
This reverts commit 4218777afb.
2024-08-06 17:34:11 +10:00
4218777afb Cut release v0.24.9 2024-08-06 17:32:39 +10:00
8b1b462e29 Revert "Cut release v0.24.9 (#3284)" (#3294)
This reverts commit 1c778bf373.
2024-08-06 17:32:18 +10:00
2bc99ba39b reset camera on empty scene (#3293)
* reset camera on empty scene

* tweak numbers

* number tweak again
2024-08-06 17:05:46 +10:00
ffe0da6dcd don't allow edit on sketches with no variable declaration (#3292)
don't allow edit on sketches with no variable decleration
2024-08-06 06:17:30 +00:00
d27afb8c74 Improve docs for x/yLine and x/yLineTo (#3285)
* Improve docs for xLineTo and yLineTo

* Improve docs for xLine and yLine
2024-08-06 14:26:19 +10:00
1c778bf373 Cut release v0.24.9 (#3284)
* Cut release v0.24.9

* Look at this (photo)Graph *in the voice of Nickelback*

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-06 13:13:34 +10:00
5df8a943a9 Easy KCL tests with no engine/visuals (#3282)
This new test framework, `no_visuals`, is for testing KCL programs via asserts, not via twenty-twenty visual tests. This is useful for unit-testing small fragments of KCL.

It's easy! All you need to do is:
- Write a KCL file
- Save it under `tests/executor/inputs/no_visuals/foo.kcl`
- Open `no_visuals.rs` and add `gen_test!(foo);`
2024-08-06 02:44:49 +00:00
ab729dbcdb Bump google-github-actions/upload-cloud-storage from 2.1.0 to 2.1.1 (#3272)
Bumps [google-github-actions/upload-cloud-storage](https://github.com/google-github-actions/upload-cloud-storage) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/google-github-actions/upload-cloud-storage/releases)
- [Changelog](https://github.com/google-github-actions/upload-cloud-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/upload-cloud-storage/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: google-github-actions/upload-cloud-storage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 10:13:00 -07:00
84865eaed0 Add assertEqual function to KCL stdlib (#3279)
Takes a tolerance, because floating-point imprecision.
2024-08-05 16:31:58 +00:00
31 changed files with 356 additions and 43 deletions

View File

@ -89,16 +89,20 @@ jobs:
- run: yarn build:wasm
- run: yarn simpleserver:ci
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
- name: Install Chromium Browser
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn playwright install chromium --with-deps
- name: run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn test:nowatch
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check
run: |
git add src/lang/std/artifactMapGraphs
@ -107,7 +111,7 @@ jobs:
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: steps.git-check.outputs.modified == 'true'
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
@ -531,7 +535,7 @@ jobs:
project_id: kittycadapi
- name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.0
uses: google-github-actions/upload-cloud-storage@v2.1.1
with:
path: artifact
glob: '*/Zoo*'
@ -539,13 +543,13 @@ jobs:
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.0
uses: google-github-actions/upload-cloud-storage@v2.1.1
with:
path: last_update.json
destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.0
uses: google-github-actions/upload-cloud-storage@v2.1.1
with:
path: last_download.json
destination: ${{ env.BUCKET_DIR }}

37
docs/kcl/assertEqual.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,7 @@ layout: manual
* [`arc`](kcl/arc)
* [`asin`](kcl/asin)
* [`assert`](kcl/assert)
* [`assertEqual`](kcl/assertEqual)
* [`assertGreaterThan`](kcl/assertGreaterThan)
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
* [`assertLessThan`](kcl/assertLessThan)

View File

@ -54791,6 +54791,62 @@
"const myVar = true\nassert(myVar, \"should always be true\")"
]
},
{
"name": "assertEqual",
"summary": "Check that a numerical value equals another at runtime,",
"description": "otherwise raise an error.",
"tags": [],
"args": [
{
"name": "left",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "right",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "epsilon",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "message",
"type": "string",
"schema": {
"type": "string"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "()",
"schema": {
"type": "null"
},
"required": true
},
"unpublished": false,
"deprecated": false,
"examples": [
"let n = 1.0285\nlet m = 1.0286\nassertEqual(n, m, 0.01, \"n is within the given tolerance for m\")"
]
},
{
"name": "assertGreaterThan",
"summary": "Check that a numerical value is greater than another at runtime,",
@ -223435,7 +223491,7 @@
},
{
"name": "xLine",
"summary": "Draw a line on the x-axis.",
"summary": "Draw a line parallel to the X-axis, with the given length.",
"description": "",
"tags": [],
"args": [
@ -230069,8 +230125,8 @@
},
{
"name": "xLineTo",
"summary": "Draw a line to a point on the x-axis.",
"description": "",
"summary": "Draw a line parallel to the X axis, that ends at the given X.",
"description": "E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)",
"tags": [],
"args": [
{
@ -236703,7 +236759,7 @@
},
{
"name": "yLine",
"summary": "Draw a line on the y-axis.",
"summary": "Draw a line parallel to the Y-axis, with the given length.",
"description": "",
"tags": [],
"args": [
@ -243337,8 +243393,8 @@
},
{
"name": "yLineTo",
"summary": "Draw a line to a point on the y-axis.",
"description": "",
"summary": "Draw a line parallel to the Y axis, that ends at the given Y.",
"description": "E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)",
"tags": [],
"args": [
{

View File

@ -1,10 +1,10 @@
---
title: "xLine"
excerpt: "Draw a line on the x-axis."
excerpt: "Draw a line parallel to the X-axis, with the given length."
layout: manual
---
Draw a line on the x-axis.
Draw a line parallel to the X-axis, with the given length.

View File

@ -1,12 +1,12 @@
---
title: "xLineTo"
excerpt: "Draw a line to a point on the x-axis."
excerpt: "Draw a line parallel to the X axis, that ends at the given X."
layout: manual
---
Draw a line to a point on the x-axis.
Draw a line parallel to the X axis, that ends at the given X.
E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)
```js
xLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup

View File

@ -1,10 +1,10 @@
---
title: "yLine"
excerpt: "Draw a line on the y-axis."
excerpt: "Draw a line parallel to the Y-axis, with the given length."
layout: manual
---
Draw a line on the y-axis.
Draw a line parallel to the Y-axis, with the given length.

View File

@ -1,12 +1,12 @@
---
title: "yLineTo"
excerpt: "Draw a line to a point on the y-axis."
excerpt: "Draw a line parallel to the Y axis, that ends at the given Y."
layout: manual
---
Draw a line to a point on the y-axis.
Draw a line parallel to the Y axis, that ends at the given Y.
E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)
```js
yLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.24.8",
"version": "0.24.9",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.17.0",

View File

@ -80,5 +80,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.24.8"
"version": "0.24.9"
}

View File

@ -791,6 +791,7 @@ export function isSingleCursorInPipe(
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const nodeTypes = pathToNode.map(([, type]) => type)
if (nodeTypes.includes('FunctionExpression')) return false
if (!nodeTypes.includes('VariableDeclaration')) return false
if (nodeTypes.includes('PipeExpression')) return true
return false
}

View File

@ -458,6 +458,9 @@ async function GraphTheGraph(
await page.waitForSelector('#plotly-graph')
const element = await page.$('#plotly-graph')
// wait an extra bit for things to settle
await new Promise((resolve) => setTimeout(resolve, 500))
// @ts-ignore
await element.screenshot({
path: `./e2e/playwright/temp3.png`,

View File

@ -366,7 +366,7 @@ export const modelingMachine = createMachine(
'Artifact graph emptied': 'hidePlanes',
},
entry: 'show default planes',
entry: ['show default planes', 'reset camera position'],
},
},
@ -1063,6 +1063,17 @@ export const modelingMachine = createMachine(
sketchEnginePathId: '',
sketchPlaneId: '',
}),
'reset camera position': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: { x: 0, y: -1250, z: 580 },
up: { x: 0, y: 0, z: 1 },
},
}),
'set new sketch metadata': assign((_, { data }) => ({
sketchDetails: data,
})),

View File

@ -2862,7 +2862,7 @@ impl MemberExpression {
// Actually evaluate memory to compute the property.
let prop = memory.get(&name, property_src)?;
let MemoryItem::UserVal(prop) = prop else {
return Err(KclError::Syntax(KclErrorDetails {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!(
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
@ -2876,17 +2876,17 @@ impl MemberExpression {
.and_then(|x| usize::try_from(x).ok())
.map(Property::Number)
.ok_or_else(|| {
KclError::Syntax(KclErrorDetails {
KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!(
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
"{name}'s value is not a valid property/index, you can only use a string or int (>= 0) here",
),
})
})?
}
JValue::String(ref x) => Property::String(x.to_owned()),
_ => {
return Err(KclError::Syntax(KclErrorDetails {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!(
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
@ -2903,7 +2903,7 @@ impl MemberExpression {
if let Ok(x) = u64::try_from(x) {
Property::Number(x.try_into().unwrap())
} else {
return Err(KclError::Syntax(KclErrorDetails {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
}));
@ -2911,7 +2911,7 @@ impl MemberExpression {
}
LiteralValue::String(s) => Property::String(s),
_ => {
return Err(KclError::Syntax(KclErrorDetails {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
}));
@ -2943,7 +2943,7 @@ impl MemberExpression {
}))
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("Property {property} not found in object"),
message: format!("Property '{property}' not found in object"),
source_ranges: vec![self.clone().into()],
}))
}
@ -2978,10 +2978,13 @@ impl MemberExpression {
),
source_ranges: vec![self.clone().into()],
})),
(_, _) => Err(KclError::Semantic(KclErrorDetails {
message: "Only arrays and objects can be indexed".to_owned(),
source_ranges: vec![self.clone().into()],
})),
(being_indexed, _) => {
let t = human_friendly_type(being_indexed);
Err(KclError::Semantic(KclErrorDetails {
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"),
source_ranges: vec![self.clone().into()],
}))
}
}
}
@ -4070,6 +4073,17 @@ impl ConstraintLevels {
}
}
fn human_friendly_type(j: JValue) -> &'static str {
match j {
JValue::Null => "null",
JValue::Bool(_) => "boolean (true/false value)",
JValue::Number(_) => "number",
JValue::String(_) => "string (text)",
JValue::Array(_) => "array (list)",
JValue::Object(_) => "object",
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

View File

@ -67,6 +67,27 @@ pub async fn assert_gt(args: Args) -> Result<MemoryItem, KclError> {
args.make_null_user_val()
}
/// Check that a numerical value equals another at runtime,
/// otherwise raise an error.
///
/// ```no_run
/// let n = 1.0285
/// let m = 1.0286
/// assertEqual(n, m, 0.01, "n is within the given tolerance for m")
/// ```
#[stdlib {
name = "assertEqual",
}]
async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, args: &Args) -> Result<(), KclError> {
_assert((right - left).abs() < epsilon, message, args).await
}
pub async fn assert_equal(args: Args) -> Result<MemoryItem, KclError> {
let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?;
inner_assert_equal(left, right, epsilon, &description, &args).await?;
args.make_null_user_val()
}
/// Check that a numerical value is greater than another at runtime,
/// otherwise raise an error.
///

View File

@ -120,6 +120,7 @@ lazy_static! {
Box::new(crate::std::math::ToRadians),
Box::new(crate::std::polar::Polar),
Box::new(crate::std::assert::Assert),
Box::new(crate::std::assert::AssertEqual),
Box::new(crate::std::assert::AssertLessThan),
Box::new(crate::std::assert::AssertGreaterThan),
Box::new(crate::std::assert::AssertLessThanOrEq),

View File

@ -168,7 +168,9 @@ pub async fn x_line_to(args: Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line to a point on the x-axis.
/// Draw a line parallel to the X axis, that ends at the given X.
/// E.g. if the previous line ended at (1, 1),
/// then xLineTo(4) draws a line from (1, 1) to (4, 1)
///
/// ```no_run
/// const exampleSketch = startSketchOn('XZ')
@ -214,7 +216,9 @@ pub async fn y_line_to(args: Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line to a point on the y-axis.
/// Draw a line parallel to the Y axis, that ends at the given Y.
/// E.g. if the previous line ended at (1, 1),
/// then yLineTo(4) draws a line from (1, 1) to (1, 4)
///
/// ```no_run
/// const exampleSketch = startSketchOn("XZ")
@ -336,7 +340,7 @@ pub async fn x_line(args: Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line on the x-axis.
/// Draw a line parallel to the X-axis, with the given length.
///
/// ```no_run
/// const exampleSketch = startSketchOn('XZ')
@ -378,7 +382,7 @@ pub async fn y_line(args: Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a line on the y-axis.
/// Draw a line parallel to the Y-axis, with the given length.
///
/// ```no_run
/// const exampleSketch = startSketchOn('XZ')

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -4,8 +4,7 @@ const arr = [0, 0, 0, 10]
const i = 3
const ten = arr[i]
assertLessThanOrEq(ten, 10, "oops")
assertGreaterThanOrEq(ten, 10, "oops2")
assertEqual(ten, 10, 0.000001, "oops")
const p = "foo"
const obj = {
@ -14,5 +13,4 @@ const obj = {
}
const one = obj[p]
assertLessThanOrEq(one, 1, "oops")
assertGreaterThanOrEq(one, 1, "oops2")
assertEqual(one, 1, 0.0000001, "oops")

View File

@ -0,0 +1,2 @@
let arr = []
let x = arr[0]

View File

@ -0,0 +1,18 @@
// This tests indexing an array.
const array = [90, 91, 92]
// Test: literal index.
const result0 = array[1]
assertLessThanOrEq(result0, 91, "Literal property lookup")
assertGreaterThanOrEq(result0, 91, "Literal property lookup")
// Test: computed index.
const i = int(1 + 0)
const result1 = array[i]
assertLessThanOrEq(result1, 91, "Computed property lookup")
assertGreaterThanOrEq(result1, 91, "Computed property lookup")

View File

@ -0,0 +1,2 @@
let arr = [1, 2, 3]
let x = arr[1.2]

View File

@ -0,0 +1,3 @@
let arr = [1, 2, 3]
let i = -1
let x = arr[i]

View File

@ -0,0 +1,2 @@
let arr = [1, 2, 3]
let x = arr["s"]

View File

@ -0,0 +1,2 @@
let num = 999
let x = num[3]

View File

@ -0,0 +1,2 @@
let b = true
let x = b["property"]

View File

@ -0,0 +1,2 @@
let obj = {key: 123}
let num = obj[3]

View File

@ -0,0 +1,2 @@
let obj = {}
let k = obj["age"]

View File

@ -0,0 +1,40 @@
// This tests evaluating properties of objects.
const obj = {
foo: 1,
bar: 0,
}
// Test: the property is a literal.
const one_a = obj["foo"]
assertLessThanOrEq(one_a, 1, "Literal property lookup")
assertGreaterThanOrEq(one_a, 1, "Literal property lookup")
// Test: the property is a variable,
// which must be evaluated before looking it up.
const p = "foo"
const one_b = obj[p]
assertLessThanOrEq(one_b, 1, "Computed property lookup")
assertGreaterThanOrEq(one_b, 1, "Computed property lookup")
// Test: multiple literal properties.
const obj2 = {
inner: obj,
}
const one_c = obj2.inner["foo"]
assertLessThanOrEq(one_c, 1, "Literal property lookup")
assertGreaterThanOrEq(one_c, 1, "Literal property lookup")
// Test: multiple properties, mix of literal and computed.
const one_d = obj2.inner[p]
assertLessThanOrEq(one_d, 1, "Computed property lookup")
assertGreaterThanOrEq(one_d, 1, "Computed property lookup")

View File

@ -4,7 +4,7 @@ use kcl_lib::{settings::types::UnitLength, test_server::execute_and_snapshot};
/// i.e. how different the current model snapshot can be from the previous saved one.
const MIN_DIFF: f64 = 0.99;
// mod server;
mod no_visuals;
macro_rules! kcl_input {
($file:literal) => {

View File

@ -0,0 +1,87 @@
use kcl_lib::{ast::types::Program, errors::KclError, executor::ExecutorContext};
macro_rules! gen_test {
($file:ident) => {
#[tokio::test]
async fn $file() {
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
run(&code).await;
}
};
}
macro_rules! gen_test_fail {
($file:ident, $expected:literal) => {
#[tokio::test]
async fn $file() {
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
let actual = run_fail(&code).await;
assert_eq!(actual.get_message(), $expected);
}
};
}
async fn run(code: &str) {
let (ctx, program) = setup(code).await;
ctx.run(&program, None).await.unwrap();
}
async fn setup(program: &str) -> (ExecutorContext, Program) {
let tokens = kcl_lib::token::lexer(program).unwrap();
let parser = kcl_lib::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let ctx = kcl_lib::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
kcl_lib::engine::conn_mock::EngineConnection::new().await.unwrap(),
)),
fs: std::sync::Arc::new(kcl_lib::fs::FileManager::new()),
stdlib: std::sync::Arc::new(kcl_lib::std::StdLib::new()),
settings: Default::default(),
is_mock: true,
};
(ctx, program)
}
async fn run_fail(code: &str) -> KclError {
let (ctx, program) = setup(code).await;
let Err(e) = ctx.run(&program, None).await else {
panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
};
e
}
gen_test!(property_of_object);
gen_test!(index_of_array);
gen_test_fail!(
invalid_index_str,
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"
);
gen_test_fail!(
invalid_index_negative,
"semantic: i's value is not a valid property/index, you can only use a string or int (>= 0) here"
);
gen_test_fail!(
invalid_index_fractional,
"semantic: Only strings or ints (>= 0) can be properties/indexes"
);
gen_test_fail!(
invalid_member_object,
"semantic: Only arrays and objects can be indexed, but you're trying to index a number"
);
gen_test_fail!(
invalid_member_object_prop,
"semantic: Only arrays and objects can be indexed, but you're trying to index a boolean (true/false value)"
);
gen_test_fail!(
non_string_key_of_object,
"semantic: Only strings can be used as the property of an object, but you're using a number"
);
gen_test_fail!(
array_index_oob,
"undefined value: The array doesn't have any item at index 0"
);
gen_test_fail!(
object_prop_not_found,
"undefined value: Property 'age' not found in object"
);