use std::time::SystemTime;
use chrono::{Local, TimeZone};
use indoc::indoc;
use liwe::model::config::{ActionDefinition, Attach, Configuration};
fn fixed_now() -> SystemTime {
Local
.with_ymd_and_hms(2026, 3, 27, 14, 30, 0)
.unwrap()
.into()
}
use crate::fixture::*;
#[test]
fn basic_attach() {
assert_attached(
indoc! {"
# title a
[title b](2)
_
# title b
_
# target
"},
2,
indoc! {"
# target
[title b](2)
"},
);
}
#[test]
fn alreary_attached() {
assert_no_action(
indoc! {"
# title a
[title b](2)
_
# title b
_
# target
[title b](2)
"},
2,
);
}
#[test]
fn attach_to_date_template() {
assert_attached_template(
indoc! {"
# title a
[title b](2)
_
# title b
"},
2,
indoc! {"
# Mar 27, 2026
[title b](2)
"},
"2026-03-27",
);
}
#[test]
fn attach_no_key() {
assert_attached_new_key(
indoc! {"
# title a
[title b](2)
_
# title b
"},
2,
indoc! {"
# template
[title b](2)
"},
);
}
#[test]
fn basic_attach_non_empty() {
assert_attached(
indoc! {"
# title a
[title b](2)
_
# title b
_
# target
[title a](1)
"},
2,
indoc! {"
# target
[title a](1)
[title b](2)
"},
);
}
#[test]
fn basic_attach_pre_header() {
assert_attached(
indoc! {"
# title a
[title b](2)
_
# title b
_
# target
## header
"},
2,
indoc! {"
# target
[title b](2)
## header
"},
);
}
#[test]
fn basic_attach_pre_header_multiple() {
assert_attached(
indoc! {"
# title a
[title b](2)
_
# title b
_
# target
[title a](1)
## header
"},
2,
indoc! {"
# target
[title a](1)
[title b](2)
## header
"},
);
}
#[test]
fn basic_attach_no_header() {
assert_attached(
indoc! {"
# title a
[title b](2)
_
# title b
_
"},
2,
indoc! {"
[title b](2)
"},
);
}
fn assert_attached(source: &str, line: u32, expected: &str) {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "3".into(),
document_template: "# template\n\n{{content}}".into(),
}),
);
Fixture::with_config(source, configuration).code_action(
uri(1).to_code_action_params(line, "custom.attach"),
vec![uri(3).to_edit(expected)]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
fn assert_attached_new_key(source: &str, line: u32, expected: &str) {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "3".into(),
document_template: "# template\n\n{{content}}".into(),
}),
);
Fixture::with_config(source, configuration).code_action(
uri(1).to_code_action_params(line, "custom.attach"),
vec![uri(3).to_create_file(), uri(3).to_edit(expected)]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
fn assert_no_action(source: &str, line: u32) {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "3".into(),
document_template: "# template\n\n{{content}}".into(),
}),
);
Fixture::with_config(source, configuration)
.no_code_action(uri(1).to_code_action_params(line, "custom.attach"));
}
fn assert_attached_template(source: &str, line: u32, expected: &str, expected_key: &str) {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "{{today}}".into(),
document_template: "# {{today}}\n\n{{content}}".into(),
}),
);
let expected_uri = uri_from(expected_key);
Fixture::with_config_and_now(source, configuration, fixed_now()).code_action(
uri(1).to_code_action_params(line, "custom.attach"),
vec![
expected_uri.clone().to_create_file(),
expected_uri.to_edit(expected),
]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
#[test]
fn attach_with_time_format() {
let mut configuration = Configuration::default();
configuration.markdown.date_format = Some("%b %d, %Y %H:%M".into());
configuration.library.date_format = Some("%Y%m%d%H%M".into());
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "{{today}}".into(),
document_template: "# {{today}}\n\n{{content}}".into(),
}),
);
let expected_uri = uri_from("202603271430");
Fixture::with_config_and_now(
indoc! {"
# title a
[title b](2)
_
# title b
"},
configuration,
fixed_now(),
)
.code_action(
uri(1).to_code_action_params(2, "custom.attach"),
vec\n"),
]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
#[test]
fn attach_with_separate_locales() {
let mut configuration = Configuration::default();
configuration.library.locale = Some("en_US".into());
configuration.library.date_format = Some("%A-%B-%d".into());
configuration.markdown.locale = Some("de_DE".into());
configuration.markdown.date_format = Some("%A, %d. %B %Y".into());
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "{{today}}".into(),
document_template: "# {{today}}\n\n{{content}}".into(),
}),
);
let expected_uri = uri_from("Friday-March-27");
Fixture::with_config_and_now(
indoc! {"
# title a
[title b](2)
_
# title b
"},
configuration,
fixed_now(),
)
.code_action(
uri(1).to_code_action_params(2, "custom.attach"),
vec\n"),
]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
#[test]
fn attach_with_separate_time_format() {
let mut configuration = Configuration::default();
configuration.library.date_format = Some("%Y-%m-%d".into());
configuration.library.time_format = Some("%Y-%m-%d-%H%M".into());
configuration.markdown.date_format = Some("%b %d, %Y".into());
configuration.markdown.time_format = Some("%b %d, %Y %H:%M".into());
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "{{today}}-{{now}}".into(),
document_template: "# {{today}}\n\nCreated: {{now}}\n\n{{content}}".into(),
}),
);
let expected_uri = uri_from("2026-03-27-2026-03-27-1430");
Fixture::with_config_and_now(
indoc! {"
# title a
[title b](2)
_
# title b
"},
configuration,
fixed_now(),
)
.code_action(
uri(1).to_code_action_params(2, "custom.attach"),
vec\n"),
]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
#[test]
fn attach_inline_link() {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "3".into(),
document_template: "# template\n\n{{content}}".into(),
}),
);
Fixture::with_config(
indoc! {"
# title a
Some text with [title b](2) link.
_
# title b
_
# target
"},
configuration,
)
.code_action(
uri(1).to_code_action_params_at_position(2, 17, "custom.attach"),
vec
"})]
.to_workspace_edit()
.to_code_action("Attach", "custom.attach"),
);
}
#[test]
fn attach_inline_link_no_action_outside_link() {
let mut configuration = Configuration::default();
configuration.actions.insert(
"attach".into(),
ActionDefinition::Attach(Attach {
title: "Attach".into(),
key_template: "3".into(),
document_template: "# template\n\n{{content}}".into(),
}),
);
Fixture::with_config(
indoc! {"
# title a
Some text with [title b](2) link.
_
# title b
_
# target
"},
configuration,
)
.no_code_action(uri(1).to_code_action_params_at_position(2, 5, "custom.attach"));
}