Skip to content

Commit

Permalink
feat(tests): Add e2e tests for language server
Browse files Browse the repository at this point in the history
  • Loading branch information
Princesseuh committed Dec 10, 2024
1 parent 2a95cc5 commit 147d868
Show file tree
Hide file tree
Showing 21 changed files with 769 additions and 863 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ codegen-units = 1
[profile.benchmark]
inherits = "release"
debug = true

[workspace.lints.clippy]
pedantic = { level = "warn", priority = -1 }
15 changes: 10 additions & 5 deletions crates/weblsp/src/css.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use crate::cast;
use csslsrs::service::LanguageService;
use lsp_server::{Connection, Message, Request, Response};
use lsp_types::request::{
ColorPresentationRequest, DocumentColor, FoldingRangeRequest, HoverRequest,
};
use std::error::Error;

use crate::requests::cast;

/// Initialize our CSS Language Service (CSSlsrs).
/// Used once at the start of the main loop, so the document store stays alive throughout the server's lifetime.
pub fn init_language_service() -> LanguageService {
Expand All @@ -18,13 +22,13 @@ pub fn handle_request(
) -> Result<(), Box<dyn Error + Sync + Send>> {
match req.method.as_str() {
"textDocument/documentColor" => {
let (id, params) = cast::<lsp_types::request::DocumentColor>(req)?;
let (id, params) = cast::<DocumentColor>(req)?;
let colors = language_service
.get_document_colors(get_text_document(params.text_document, language_service)?);
send_result(connection, id, serde_json::to_value(&colors).unwrap())?;
}
"textDocument/colorPresentation" => {
let (id, params) = cast::<lsp_types::request::ColorPresentationRequest>(req)?;
let (id, params) = cast::<ColorPresentationRequest>(req)?;
let presentations =
language_service.get_color_presentations(lsp_types::ColorInformation {
color: params.color,
Expand All @@ -37,13 +41,13 @@ pub fn handle_request(
)?;
}
"textDocument/foldingRange" => {
let (id, params) = cast::<lsp_types::request::FoldingRangeRequest>(req)?;
let (id, params) = cast::<FoldingRangeRequest>(req)?;
let ranges = language_service
.get_folding_ranges(get_text_document(params.text_document, language_service)?);
send_result(connection, id, serde_json::to_value(&ranges).unwrap())?;
}
"textDocument/hover" => {
let (id, params) = cast::<lsp_types::request::HoverRequest>(req)?;
let (id, params) = cast::<HoverRequest>(req)?;
let hover = language_service.get_hover(
get_text_document(
params.text_document_position_params.text_document,
Expand All @@ -69,6 +73,7 @@ fn get_text_document(
None => return Err(Box::from("Document not found")),
};

// TODO: It'd be great to avoid cloning the document here, might need to refactor methods to take a reference instead.
Ok(text_document.document.clone())
}

Expand Down
38 changes: 13 additions & 25 deletions crates/weblsp/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod css;
pub mod notifications;
pub mod requests;
use lsp_server::{Connection, ExtractError, Message, Request, RequestId};
mod css;
mod notifications;
mod requests;
mod response;
use lsp_server::{Connection, Message};
use lsp_types::{InitializeParams, ServerCapabilities, TextDocumentSyncCapability};
use std::error::Error;

Expand All @@ -26,20 +27,24 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
..Default::default()
})
.unwrap();

let initialization_params = match connection.initialize(server_capabilities) {
Ok(it) => it,
Ok(params) => params,
Err(e) => {
if e.channel_is_disconnected() {
io_threads.join()?;
}
return Err(e.into());
}
};

// Init language services and start the main loop.
let css_language_service = css::init_language_service();
main_loop(connection, initialization_params, css_language_service)?;

// Joins the IO threads to ensure all communication is properly finished.
io_threads.join()?;

// Shut down gracefully.
eprintln!("shutting down server");
Ok(())
Expand All @@ -48,10 +53,10 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
/// Main loop of our WEBlsp server. Handles all incoming messages, and dispatches them to the appropriate language handler.
fn main_loop(
connection: Connection,
params: serde_json::Value,
init_params: serde_json::Value,
mut css_language_service: csslsrs::service::LanguageService,
) -> Result<(), Box<dyn Error + Sync + Send>> {
let _params: InitializeParams = serde_json::from_value(params).unwrap();
let _init_params: InitializeParams = serde_json::from_value(init_params).unwrap();
for msg in &connection.receiver {
eprintln!("new msg: {msg:?}");
match msg {
Expand All @@ -60,7 +65,7 @@ fn main_loop(
continue;
}
Message::Response(resp) => {
handle_response(resp)?;
response::handle_response(resp)?;
continue;
}
Message::Notification(not) => {
Expand All @@ -71,20 +76,3 @@ fn main_loop(
}
Ok(())
}

/// TMP: log the response.
fn handle_response(resp: lsp_server::Response) -> Result<(), Box<dyn Error + Sync + Send>> {
eprintln!("handle_response: got {resp:?}");
Ok(())
}

/// Attempts to cast a request to a specific LSP request type.
/// If the request is not of the specified type, an error will be returned.
/// If the request is of the specified type, the request ID and parameters will be returned.
pub fn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>>
where
R: lsp_types::request::Request,
R::Params: serde::de::DeserializeOwned,
{
req.extract(R::METHOD)
}
19 changes: 14 additions & 5 deletions crates/weblsp/src/requests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use lsp_server::Connection;
use lsp_server::{Connection, ExtractError, Request, RequestId};
use std::error::Error;
use std::str::FromStr;

Expand Down Expand Up @@ -36,12 +36,10 @@ fn get_language_id(
.get("textDocument")
.and_then(|td| td.get("uri"))
.and_then(|uri| uri.as_str())
.and_then(|uri| lsp_types::Uri::from_str(uri).ok())
.ok_or("Missing or invalid 'textDocument.uri' in request parameters")?;

let text_document_uri = lsp_types::Uri::from_str(text_document_identifier)
.map_err(|_| "Invalid 'textDocument.uri' in request parameters")?;

let store_entry = match css_language_service.get_document(&text_document_uri) {
let store_entry = match css_language_service.get_document(&text_document_identifier) {
Some(doc) => doc,
None => return Err(Box::from("Document not found")),
};
Expand All @@ -52,3 +50,14 @@ fn get_language_id(
// The immutable borrow ends here
Ok(language_id)
}

/// Attempts to cast a request to a specific LSP request type.
/// If the request is not of the specified type, an error will be returned.
/// If the request is of the specified type, the request ID and parameters will be returned.
pub fn cast<R>(req: Request) -> Result<(RequestId, R::Params), ExtractError<Request>>
where
R: lsp_types::request::Request,
R::Params: serde::de::DeserializeOwned,
{
req.extract(R::METHOD)
}
7 changes: 7 additions & 0 deletions crates/weblsp/src/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::error::Error;

/// TMP: log the response.
pub fn handle_response(resp: lsp_server::Response) -> Result<(), Box<dyn Error + Sync + Send>> {
eprintln!("handle_response: got {resp:?}");
Ok(())
}
4 changes: 3 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ test:
echo "Running Rust tests..."
cargo test
echo "Running JS tests..."
pnpm -C ./packages/csslsrs run test
pnpm -C ./packages/csslsrs run test run
cargo build --package weblsp
pnpm -C ./packages/language-server-tests run test run

benchmark:
echo "Running Native benchmarks..."
Expand Down
10 changes: 3 additions & 7 deletions packages/csslsrs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"scripts": {
"prepublishOnly": "just build-wasm release && pnpm run build",
"build": "node ./build.js && tsc",
"test": "mocha --require tsx test/**/*.test.ts"
"test": "vitest"
},
"files": [
"dist"
Expand All @@ -26,12 +26,8 @@
"vscode-languageserver-types": "^3.17.5"
},
"devDependencies": {
"@types/chai": "^5.0.0",
"@types/mocha": "^10.0.9",
"@types/node": "^22.8.0",
"chai": "^5.1.2",
"mocha": "^10.7.3",
"tsx": "^4.19.1",
"typescript": "^5.6.3"
"typescript": "^5.6.3",
"vitest": "^2.1.8"
}
}
5 changes: 2 additions & 3 deletions packages/csslsrs/test/features/colors.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { describe, it } from "mocha";
import { expect } from "chai";
import { describe, it, expect, beforeAll } from "vitest";
import { TextDocument } from "vscode-languageserver-textdocument";
import { LanguageService } from "../../dist";

describe("Colors", () => {
let ls: LanguageService;
let document: TextDocument;

before(() => {
beforeAll(() => {
ls = new LanguageService({
include_base_css_custom_data: true,
});
Expand Down
3 changes: 1 addition & 2 deletions packages/csslsrs/test/features/folding.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { describe, it } from "mocha";
import { expect } from "chai";
import { describe, it, expect } from "vitest";
import { LanguageService } from "../../dist/index.js";
import { TextDocument } from "vscode-languageserver-textdocument";
import type { FoldingRange } from "vscode-languageserver-types";
Expand Down
5 changes: 2 additions & 3 deletions packages/csslsrs/test/features/hover.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { describe, it } from "mocha";
import { expect } from "chai";
import { describe, it, expect, beforeAll } from "vitest";
import { TextDocument } from "vscode-languageserver-textdocument";
import { LanguageService } from "../../../csslsrs/dist/index";

describe("Hover", () => {
let ls: LanguageService;
let document: TextDocument;

before(() => {
beforeAll(() => {
ls = new LanguageService({
include_base_css_custom_data: true,
});
Expand Down
Empty file.
16 changes: 16 additions & 0 deletions packages/language-server-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "language-server-tests",
"type": "module",
"private": true,
"scripts": {
"test": "vitest"
},
"dependencies": {
"@types/node": "^22.10.1",
"vitest": "^2.1.8",
"vscode-languageclient": "^9.0.1",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.12",
"vscode-uri": "^3.0.8"
}
}
23 changes: 23 additions & 0 deletions packages/language-server-tests/tests/css/colors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";

describe("CSS - Colors", () => {
it("should return something for a document colors request", async () => {
const doc = await languageServer.openFakeDocument(
`h1 { color: red; }`,
"css"
);

const colors = await languageServer.sendDocumentColorRequest(doc.uri);

expect(colors).toBeDefined();

const colorPresentations =
await languageServer.sendColorPresentationRequest(
doc.uri,
colors[0].color,
colors[0].range
);

expect(colorPresentations).toBeDefined();
});
});
16 changes: 16 additions & 0 deletions packages/language-server-tests/tests/css/folding.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";

describe("CSS - Folding Ranges", () => {
it("should return something for a folding ranges request", async () => {
const doc = await languageServer.openFakeDocument(
`h1 { color: red; }`,
"css"
);

const foldingRanges = await languageServer.sendFoldingRangesRequest(
doc.uri
);

expect(foldingRanges).toBeDefined();
});
});
18 changes: 18 additions & 0 deletions packages/language-server-tests/tests/css/hover.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { Position } from "vscode-languageserver-protocol";

describe("CSS - Hover", () => {
it("should return something for a hover request", async () => {
const doc = await languageServer.openFakeDocument(
`h1 { color: red; }`,
"css"
);

const hover = await languageServer.sendHoverRequest(
doc.uri,
Position.create(0, 5)
);

expect(hover).toBeDefined();
});
});
21 changes: 21 additions & 0 deletions packages/language-server-tests/tests/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from "vitest";
import { ServerCapabilities } from "vscode-languageserver-protocol/node";

describe("Language server initilization", () => {
it("Can init server", async () => {
expect(languageServer).toBeDefined();
});

it("Has proper capabilities", async () => {
const capabilities: ServerCapabilities = {
colorProvider: true,
foldingRangeProvider: true,
hoverProvider: true,
textDocumentSync: 1,
};

expect(languageServer.initializeResult.capabilities).to.deep.equal(
capabilities
);
});
});
Loading

0 comments on commit 147d868

Please sign in to comment.