Compare commits

..

1 Commits

27 changed files with 2431 additions and 857 deletions

View File

@ -234,16 +234,10 @@ jobs:
shardTotal: 8
- os: namespace-profile-macos-8-cores
shardIndex: 1
shardTotal: 2
- os: namespace-profile-macos-8-cores
shardIndex: 2
shardTotal: 2
shardTotal: 1
- os: windows-latest-8-cores
shardIndex: 1
shardTotal: 2
- os: windows-latest-8-cores
shardIndex: 2
shardTotal: 2
shardTotal: 1
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

View File

@ -155,12 +155,6 @@ export class CmdBarFixture {
}
}
closeCmdBar = async () => {
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
await cmdBarCloseBtn.click()
await expect(this.cmdBarElement).not.toBeVisible()
}
get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search')
}
@ -304,27 +298,4 @@ export class CmdBarFixture {
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
)
}
async toBeOpened() {
// Check that the command bar is opened
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) {
// Check the placeholder project name exists
const actualArgument = await this.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = value
expect(actualArgument).toBe(expectedArgument)
}
async expectCommandName(value: string) {
// Check the placeholder project name exists
const actual = await this.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = value
expect(actual).toBe(expected)
}
}

View File

@ -24,7 +24,6 @@ export class HomePageFixture {
projectTextName!: Locator
sortByDateBtn!: Locator
sortByNameBtn!: Locator
appHeader!: Locator
tutorialBtn!: Locator
constructor(page: Page) {
@ -45,7 +44,6 @@ export class HomePageFixture {
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
this.appHeader = this.page.getByTestId('app-header')
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
}
@ -127,11 +125,4 @@ export class HomePageFixture {
await this.createAndGoToProject(name)
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}

View File

@ -46,7 +46,6 @@ export class SceneFixture {
public networkToggleConnected!: Locator
public engineConnectionsSpinner!: Locator
public startEditSketchBtn!: Locator
public appHeader!: Locator
constructor(page: Page) {
this.page = page
@ -58,7 +57,6 @@ export class SceneFixture {
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
this.appHeader = this.page.getByTestId('app-header')
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
@ -282,13 +280,6 @@ export class SceneFixture {
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}
function isColourArray(

File diff suppressed because it is too large Load Diff

View File

@ -3496,73 +3496,6 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
).toBeVisible()
})
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
test('Feature tree shows correct sketch count per file', async ({
context,
homePage,
scene,
toolbar,
cmdBar,
page,
}) => {
const u = await getUtils(page)
// Setup project with files.
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|> line(end = [132.84, -151.31])
|> line(end = [25.51, 167.15])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|> line(end = [73.9, -152.19])
|> line(end = [85.33, 135.48])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|> line(end = [132.84, -112.6])
|> line(end = [85.33, 234.01])
|> line(enfd = [-137.23, -54.55])`
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'multi-file-sketch-test')
await fs.mkdir(projectDir, { recursive: true })
await Promise.all([
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
])
})
await page.setBodyDimensions({ width: 1000, height: 800 })
await homePage.openProject('multi-file-sketch-test')
await scene.connectionEstablished()
await scene.settled(cmdBar)
await u.closeDebugPanel()
await toolbar.openFeatureTreePane()
await toolbar.openPane('files')
await toolbar.openFile('good.kcl')
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(2)
await toolbar.openFile('error.kcl')
// Ensure filetree is populated
await scene.settled(cmdBar)
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(0)
})
test('adding a syntax error, recovers after fixing', async ({
page,
homePage,

View File

@ -21,7 +21,6 @@ export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test'
@ -1150,77 +1149,3 @@ export function perProjectSettingsToToml(
// eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any)
}
export async function clickElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const clickWasTriggered = await tronApp.electron.evaluate(
async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
menu.click()
return true
},
menuId
)
expect(clickWasTriggered).toBe(true)
}
export async function findElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
return true
}, menuId)
expect(found).toBe(true)
}
export async function openSettingsExpectText(page: Page, text: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(text)
await expect(actualText).toBeVisible()
}
export async function openSettingsExpectLocator(page: Page, selector: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const settingsLocator = settings.locator(selector)
await expect(settingsLocator).toBeVisible()
}
/**
* A developer helper function to make playwright send all the console logs to stdout
* Call this within your E2E test and pass in the page or the tronApp to get as many
* logs piped to stdout for debugging
*/
export async function enableConsoleLogEverything({
page,
tronApp,
}: { page?: Page; tronApp?: ElectronZoo }) {
page?.on('console', (msg) => {
console.log(`[Page-log]: ${msg.text()}`)
})
tronApp?.electron.on('window', async (electronPage) => {
electronPage.on('console', (msg) => {
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
})
})
tronApp?.electron.on('console', (msg) => {
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
})
}

View File

@ -237,7 +237,7 @@ test.describe('Testing segment overlays', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
@ -402,7 +402,7 @@ test.describe('Testing segment overlays', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(9)
await expect(page.getByTestId('segment-overlay')).toHaveCount(8)
const clickUnconstrained = _clickUnconstrained(page, editor)
@ -482,7 +482,7 @@ test.describe('Testing segment overlays', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
@ -602,7 +602,7 @@ test.describe('Testing segment overlays', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(14)
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
@ -808,7 +808,7 @@ profile001 = startProfile(sketch001, at = [56.37, 120.33])
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(6)
await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
const clickUnconstrained = _clickUnconstrained(page, editor)
const clickConstrained = _clickConstrained(page, editor)
@ -1307,7 +1307,7 @@ part001 = startSketchOn(XZ)
.toBe(true)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
const segmentToDelete = await u.getBoundingBox(
`[data-overlay-index="0"]`
)

View File

@ -1249,7 +1249,7 @@ secondSketch = startSketchOn(part001, face = '')
let err = err.as_kcl_error().unwrap();
assert_eq!(
err.message(),
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string"
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string (text)"
);
}
@ -1882,7 +1882,7 @@ someFunction('INVALID')
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string (text)" }"#
);
}

View File

@ -756,12 +756,31 @@ pub(super) fn build_artifact_graph(
}
for exec_artifact in exec_artifacts.values() {
merge_artifact_into_map(&mut map, exec_artifact.clone());
let mut new_artifact = exec_artifact.clone();
// Fill in NodePaths for artifacts that were added directly to the map
// during execution.
fill_in_node_paths(&mut new_artifact, ast);
merge_artifact_into_map(&mut map, new_artifact);
}
Ok(ArtifactGraph { map })
}
/// These may have been created with placeholder `CodeRef`s because we didn't
/// have the entire AST available. Now we fill them in.
fn fill_in_node_paths(artifact: &mut Artifact, program: &Node<Program>) {
match artifact {
Artifact::StartSketchOnFace(face) => {
face.code_ref.node_path = NodePath::from_range(program, face.code_ref.range).unwrap_or_default();
}
Artifact::StartSketchOnPlane(plane) => {
plane.code_ref.node_path = NodePath::from_range(program, plane.code_ref.range).unwrap_or_default();
}
_ => {}
}
}
/// Flatten the responses into a map of command IDs to modeling command
/// responses. The raw responses from the engine contain batches.
fn flatten_modeling_command_responses(

View File

@ -913,9 +913,11 @@ impl Node<MemberExpression> {
}),
(being_indexed, _, _) => {
let t = being_indexed.human_friendly_type();
let article = article_for(&t);
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
message: format!(
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
),
source_ranges: vec![self.clone().into()],
}))
}
@ -1696,9 +1698,8 @@ impl Node<ObjectExpression> {
}
}
fn article_for<S: AsRef<str>>(s: S) -> &'static str {
// '[' is included since it's an array.
if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
fn article_for(s: &str) -> &'static str {
if s.starts_with(['a', 'e', 'i', 'o', 'u']) {
"an"
} else {
"a"
@ -1708,9 +1709,10 @@ fn article_for<S: AsRef<str>>(s: S) -> &'static str {
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
v.as_ty_f64().ok_or_else(|| {
let actual_type = v.human_friendly_type();
let article = article_for(actual_type);
KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Expected a number, but found {actual_type}",),
message: format!("Expected a number, but found {article} {actual_type}",),
})
})
}
@ -2444,23 +2446,19 @@ arr1 = [42]: [number(cm)]
a = 42: string
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce number(default units) value to type string"),
"Expected error but found {err:?}"
);
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type string"));
let program = r#"
a = 42: Plane
"#;
let result = parse_execute(program).await;
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce number(default units) value to type Plane"),
"Expected error but found {err:?}"
);
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type Plane"));
let program = r#"
arr = [0]: [string]
@ -2469,7 +2467,7 @@ arr = [0]: [string]
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce [any; 1] value to type [string]"),
.contains("could not coerce array (list) value to type [string]"),
"Expected error but found {err:?}"
);
@ -2480,7 +2478,7 @@ mixedArr = [0, "a"]: [number(mm)]
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce [any; 2] value to type [number(mm)]"),
.contains("could not coerce array (list) value to type [number(mm)]"),
"Expected error but found {err:?}"
);
}

View File

@ -280,10 +280,7 @@ impl KclValue {
/// Human readable type name used in error messages. Should not be relied
/// on for program logic.
pub(crate) fn human_friendly_type(&self) -> String {
if let Some(t) = self.principal_type() {
return t.to_string();
}
pub(crate) fn human_friendly_type(&self) -> &'static str {
match self {
KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator",
@ -317,7 +314,6 @@ impl KclValue {
KclValue::Type { .. } => "type",
KclValue::KclNone { .. } => "None",
}
.to_owned()
}
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {

View File

@ -1910,13 +1910,13 @@ notNull = !myNull
"#;
assert_eq!(
parse_execute(code1).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number(default units)",
"Cannot apply unary operator ! to non-boolean value: number",
);
let code2 = "notZero = !0";
assert_eq!(
parse_execute(code2).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number(default units)",
"Cannot apply unary operator ! to non-boolean value: number",
);
let code3 = r#"
@ -1924,7 +1924,7 @@ notEmptyString = !""
"#;
assert_eq!(
parse_execute(code3).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: string",
"Cannot apply unary operator ! to non-boolean value: string (text)",
);
let code4 = r#"
@ -1933,7 +1933,7 @@ notMember = !obj.a
"#;
assert_eq!(
parse_execute(code4).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number(default units)",
"Cannot apply unary operator ! to non-boolean value: number",
);
let code5 = "
@ -1941,7 +1941,7 @@ a = []
notArray = !a";
assert_eq!(
parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: [any; 0]",
"Cannot apply unary operator ! to non-boolean value: array (list)",
);
let code6 = "
@ -1949,7 +1949,7 @@ x = {}
notObject = !x";
assert_eq!(
parse_execute(code6).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: { }",
"Cannot apply unary operator ! to non-boolean value: object",
);
let code7 = "
@ -1975,7 +1975,7 @@ notTagDeclarator = !myTagDeclarator";
assert!(
tag_declarator_err
.message()
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
.starts_with("Cannot apply unary operator ! to non-boolean value: TagDeclarator"),
"Actual error: {:?}",
tag_declarator_err
);
@ -1989,7 +1989,7 @@ notTagIdentifier = !myTag";
assert!(
tag_identifier_err
.message()
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
.starts_with("Cannot apply unary operator ! to non-boolean value: TagIdentifier"),
"Actual error: {:?}",
tag_identifier_err
);

View File

@ -257,22 +257,14 @@ impl Args {
};
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let actual_type_name = arg.value.human_friendly_type();
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,
@ -389,22 +381,14 @@ impl Args {
}))?;
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let actual_type_name = arg.value.human_friendly_type();
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,

View File

@ -4,7 +4,8 @@ description: Error from executing argument_error.kcl
---
KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found [any; 2]
× semantic: f requires a value with type `fn(any): any`, but found array
│ (list)
╭─[5:1]
4 │
5 │ map(f, f = [0, 1])
@ -15,7 +16,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found
[any; 2]
array (list)
╭─[5:12]
4 │
5 │ map(f, f = [0, 1])

View File

@ -5,7 +5,7 @@ description: Error from executing array_elem_pop_empty_fail.kcl
KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value with
│ type `[any; 1+]`, but found [any; 0]
│ type `[any; 1+]`, but found array (list)
╭─[2:8]
1 │ arr = []
2 │ fail = pop(arr)
@ -16,7 +16,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value
│ with type `[any; 1+]`, but found [any; 0]
│ with type `[any; 1+]`, but found array (list)
╭─[2:12]
1 │ arr = []
2 │ fail = pop(arr)

View File

@ -4,7 +4,7 @@ description: Error from executing comparisons_multiple.kcl
---
KCL Semantic error
× semantic: Expected a number, but found bool
× semantic: Expected a number, but found a boolean (true/false value)
╭────
1 │ assert(3 == 3 == 3, error = "this should not compile")
· ───┬──

View File

@ -5,7 +5,7 @@ description: Error from executing error_inside_fn_also_has_source_range_of_call_
KCL Semantic error
× semantic: This function expected the input argument to be Solid or Plane
│ but it's actually of type string
│ but it's actually of type string (text)
╭─[3:23]
2 │ fn someNestedFunction(@something2) {
3 │ startSketchOn(something2)
@ -25,7 +25,7 @@ KCL Semantic error
├─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or
│ │ Plane but it's actually of type string
│ │ Plane but it's actually of type string (text)
│ ╭─[3:23]
│ 2 │ fn someNestedFunction(@something2) {
│ 3 │ startSketchOn(something2)
@ -37,7 +37,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or
│ Plane but it's actually of type string
│ Plane but it's actually of type string (text)
╭─[6:5]
5 │
6 │ someNestedFunction(something)

View File

@ -1,11 +1,11 @@
---
source: kcl-lib/src/simulation_tests.rs
source: kcl/src/simulation_tests.rs
description: Error from executing invalid_member_object.kcl
---
KCL Semantic error
× semantic: Only arrays can be indexed, but you're trying to index a
number(default units)
× semantic: Only arrays and objects can be indexed, but you're trying to
index a number
╭─[2:5]
1 │ num = 999
2 │ x = num[3]

View File

@ -5,7 +5,7 @@ description: Error from executing panic_repro_cube.kcl
KCL Semantic error
× semantic: This function expected the input argument to be tag identifier
│ but it's actually of type tag
│ but it's actually of type Unique ID (uuid)
╭─[43:25]
42 │ // these double wrapped functions are the point of this test
43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)),

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef } from 'react'
import toast from 'react-hot-toast'
import { useHotkeys } from 'react-hotkeys-hook'
import ModalContainer from 'react-modal-promise'
@ -42,7 +42,6 @@ import {
ONBOARDING_TOAST_ID,
TutorialRequestToast,
} from '@src/routes/Onboarding/utils'
import { reportRejection } from '@src/lib/trap'
// CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -53,7 +52,6 @@ maybeWriteToDisk()
export function App() {
const { project, file } = useLoaderData() as IndexLoaderData
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
useCreateFileLinkQuery((argDefaultValues) => {
@ -147,25 +145,12 @@ export function App() {
}
}, [location, settings.app.onboardingStatus, navigate])
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron
.createModelingPageMenu()
.then(() => {
setNativeFileMenuCreated(true)
})
.catch(reportRejection)
}
}, [])
return (
<div className="relative h-full flex flex-col" ref={ref}>
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
>
<CommandBarOpenButton />
<ShareButton />

View File

@ -14,7 +14,6 @@ interface AppHeaderProps extends React.PropsWithChildren {
className?: string
enableMenu?: boolean
style?: React.CSSProperties
nativeFileMenuCreated: boolean
}
export const AppHeader = ({
@ -24,14 +23,12 @@ export const AppHeader = ({
className = '',
style,
enableMenu = false,
nativeFileMenuCreated,
}: AppHeaderProps) => {
const user = useUser()
return (
<header
id="app-header"
data-testid="app-header"
className={
'w-full grid ' +
styles.header +
@ -40,7 +37,6 @@ export const AppHeader = ({
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
className
}
data-native-file-menu={nativeFileMenuCreated}
style={style}
>
<ProjectSidebarMenu

View File

@ -169,7 +169,6 @@ export const CommandBar = () => {
)}
<div className="flex flex-col gap-2 !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent">
<button
data-testid="command-bar-close-button"
onClick={() => commandBarActor.send({ type: 'Close' })}
className="group m-0 p-0 border-none bg-transparent hover:bg-transparent"
>

View File

@ -29,7 +29,7 @@ import { kclCommands } from '@src/lib/kclCommands'
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
import { markOnce } from '@src/lib/performance'
import { codeManager, kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { err, reportRejection } from '@src/lib/trap'
import { type IndexLoaderData } from '@src/lib/types'
import { useSettings, useToken } from '@src/lib/singletons'
import { commandBarActor } from '@src/lib/singletons'
@ -59,6 +59,12 @@ export const FileMachineProvider = ({
const { project, file } = projectData
const filePath = useAbsoluteFilePath()
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron.createModelingPageMenu().catch(reportRejection)
}
}, [])
useEffect(() => {
const {

View File

@ -147,10 +147,6 @@ export class KclManager {
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
// These belonged to the previous file
this.lastSuccessfulOperations = []
this.lastSuccessfulVariables = {}
}
get variables() {

View File

@ -1,5 +1,5 @@
import type { FormEvent, HTMLProps } from 'react'
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import { toast } from 'react-hot-toast'
import { useHotkeys } from 'react-hotkeys-hook'
import {
@ -70,20 +70,13 @@ type ReadWriteProjectState = {
// This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => {
const navigate = useNavigate()
const readWriteProjectDir = useCanReadWriteProjectDirectory()
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
const apiToken = useToken()
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron
.createHomePageMenu()
.then(() => {
setNativeFileMenuCreated(true)
})
.catch(reportRejection)
window.electron.createHomePageMenu().catch(reportRejection)
}
billingActor.send({ type: BillingTransition.Update, apiToken })
}, [])
@ -101,6 +94,7 @@ const Home = () => {
})
const location = useLocation()
const navigate = useNavigate()
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus.current
@ -223,10 +217,7 @@ const Home = () => {
return (
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
<AppHeader
nativeFileMenuCreated={nativeFileMenuCreated}
showToolbar={false}
/>
<AppHeader showToolbar={false} />
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl mb-12 px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
<HomeHeader
setQuery={setQuery}

View File

@ -32,16 +32,13 @@ export const Settings = () => {
useEffect(() => {
console.log('hash', location.hash)
if (location.hash) {
setTimeout(() => {
// GOTCHA: Next tick required, you can instantly navigate to a path and this code will find a null element and not scroll into view.
const element = document.getElementById(location.hash.slice(1))
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
;(
element.querySelector('input, select, textarea') as HTMLInputElement
)?.focus()
}
}, 0)
const element = document.getElementById(location.hash.slice(1))
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
;(
element.querySelector('input, select, textarea') as HTMLInputElement
)?.focus()
}
}
}, [location.hash])