the addon arc: the AddOns folder opens
- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect - both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list - the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand - the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,8 +59,7 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
|
||||
s.fire_event("QUEST_DETAIL", vec![]);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaQuestFrame:IsVisible()")
|
||||
.unwrap(),
|
||||
s.eval::<bool>("return QuestFrame:IsVisible()").unwrap(),
|
||||
"the questgiver window opened onto the left slot"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -73,8 +72,7 @@ fn questgiver_show_hide_plays_open_and_close_kits() {
|
||||
s.fire_event("QUEST_FINISHED", vec![]);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
!s.eval::<bool>("return BenillaQuestFrame:IsVisible()")
|
||||
.unwrap(),
|
||||
!s.eval::<bool>("return QuestFrame:IsVisible()").unwrap(),
|
||||
"QUEST_FINISHED hid the questgiver window"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -100,10 +98,10 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
load_xml(&s, "QuestFrame.xml");
|
||||
|
||||
let panels = [
|
||||
"BenillaQuestGreetingPanel",
|
||||
"BenillaQuestDetailPanel",
|
||||
"BenillaQuestProgressPanel",
|
||||
"BenillaQuestRewardPanel",
|
||||
"QuestGreetingPanel",
|
||||
"QuestDetailPanel",
|
||||
"QuestProgressPanel",
|
||||
"QuestRewardPanel",
|
||||
];
|
||||
let is_shown =
|
||||
|s: &UiScript, name: &str| s.eval::<bool>(&format!("return {name}:IsShown()")).unwrap();
|
||||
@@ -122,12 +120,12 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
for name in panels {
|
||||
assert_eq!(
|
||||
is_shown(&s, name),
|
||||
name == "BenillaQuestGreetingPanel",
|
||||
name == "QuestGreetingPanel",
|
||||
"only the greeting panel is shown after QUEST_GREETING"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestNpcNameText:GetText()")
|
||||
s.eval::<String>("return QuestNpcNameText:GetText()")
|
||||
.unwrap(),
|
||||
"Deputy Willem"
|
||||
);
|
||||
@@ -148,7 +146,7 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
for name in panels {
|
||||
assert_eq!(
|
||||
is_shown(&s, name),
|
||||
name == "BenillaQuestDetailPanel",
|
||||
name == "QuestDetailPanel",
|
||||
"only the detail panel is shown after QUEST_DETAIL"
|
||||
);
|
||||
}
|
||||
@@ -160,7 +158,7 @@ fn panel_events_show_exactly_one_child_panel_and_hide_the_others() {
|
||||
|
||||
/// The reward/choice grid, ported from the ref's shared `QuestFrameItems_Update` (the SAME idiom the
|
||||
/// quest log's own reward rows already prove out) — now on REAL per-panel widgets
|
||||
/// (`BenillaQuestDetailChoice*`/`BenillaQuestDetailReward*`), exercised on the Detail panel with the
|
||||
/// (`QuestDetailChoice*`/`QuestDetailReward*`), exercised on the Detail panel with the
|
||||
/// fixture shape the task's capture drives: 2 choices + 1 fixed reward + money.
|
||||
#[test]
|
||||
fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
@@ -198,56 +196,55 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestDetailChoice1:IsShown()")
|
||||
.eval::<bool>("return QuestDetailChoice1:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestDetailChoice2:IsShown()")
|
||||
.eval::<bool>("return QuestDetailChoice2:IsShown()")
|
||||
.unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return BenillaQuestDetailChoice3:IsShown()")
|
||||
.eval::<bool>("return QuestDetailChoice3:IsShown()")
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestDetailChoice1Name:GetText()")
|
||||
s.eval::<String>("return QuestDetailChoice1Name:GetText()")
|
||||
.unwrap(),
|
||||
"Worn Sword"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestDetailItemChooseText:GetText()")
|
||||
s.eval::<String>("return QuestDetailItemChooseText:GetText()")
|
||||
.unwrap(),
|
||||
"You may choose one of these rewards:"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestDetailItemReceiveText:GetText()")
|
||||
s.eval::<String>("return QuestDetailItemReceiveText:GetText()")
|
||||
.unwrap(),
|
||||
"You will also receive:",
|
||||
"choices present -> the ref's \"also\" wording (QuestFrame.lua:461-462)"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestDetailReward1:IsShown()")
|
||||
.eval::<bool>("return QuestDetailReward1:IsShown()")
|
||||
.unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return BenillaQuestDetailReward2:IsShown()")
|
||||
.eval::<bool>("return QuestDetailReward2:IsShown()")
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestDetailReward1Name:GetText()")
|
||||
s.eval::<String>("return QuestDetailReward1Name:GetText()")
|
||||
.unwrap(),
|
||||
"Militia Hammer"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BenillaQuestDetailRewardTitleText:GetText()")
|
||||
s.eval::<String>("return QuestDetailRewardTitleText:GetText()")
|
||||
.unwrap(),
|
||||
"Rewards"
|
||||
);
|
||||
|
||||
// The detail panel's choice rows are informational only (ref: only the REWARD panel's rows
|
||||
// select). They DO carry an OnClick now (`BenillaQuestItem_OnClick`, the ref's own
|
||||
// select). They DO carry an OnClick now (`QuestItem_OnClick`, the ref's own
|
||||
// QuestItemTemplate script — the ctrl/shift fork, decisions 1059/1060), but it has no select
|
||||
// arm at all: an unmodified click must not raise and must not set an itemChoice.
|
||||
s.run("BenillaQuestDetailChoice1:Click()").ok();
|
||||
s.run("QuestDetailChoice1:Click()").ok();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return BenillaQuestFrame.itemChoice")
|
||||
.unwrap(),
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
0,
|
||||
"a detail-panel row never selects a reward"
|
||||
);
|
||||
@@ -288,26 +285,26 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
assert!(
|
||||
!s.eval::<bool>("return BenillaQuestRewardChoiceHighlight:IsShown()")
|
||||
!s.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
.unwrap(),
|
||||
"no row picked yet"
|
||||
);
|
||||
// Completing without a choice picked is a no-op (ref: QuestChooseRewardError guard).
|
||||
s.run("BenillaQuestRewardCompleteButton:Click()").unwrap();
|
||||
s.run("QuestRewardCompleteButton:Click()").unwrap();
|
||||
assert!(
|
||||
s.take_quest_actions().is_empty(),
|
||||
"must pick a choice first"
|
||||
);
|
||||
|
||||
s.run("BenillaQuestRewardChoice2:Click()").unwrap();
|
||||
s.run("QuestRewardChoice2:Click()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
s.eval::<bool>("return BenillaQuestRewardChoiceHighlight:IsShown()")
|
||||
s.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
.unwrap(),
|
||||
"picking a choice row shows the highlight"
|
||||
);
|
||||
|
||||
s.run("BenillaQuestRewardCompleteButton:Click()").unwrap();
|
||||
s.run("QuestRewardCompleteButton:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.take_quest_actions(),
|
||||
vec![benilla_ui::script::QuestAction::Reward(1)],
|
||||
@@ -337,15 +334,12 @@ fn greeting_goodbye_button_closes_the_window() {
|
||||
vec![ScriptValue::Str("Deputy Willem".into())],
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestFrame:IsVisible()")
|
||||
.unwrap());
|
||||
assert!(s.eval::<bool>("return QuestFrame:IsVisible()").unwrap());
|
||||
|
||||
s.run("BenillaQuestGreetingGoodbyeButton:Click()").unwrap();
|
||||
s.run("QuestGreetingGoodbyeButton:Click()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(
|
||||
!s.eval::<bool>("return BenillaQuestFrame:IsVisible()")
|
||||
.unwrap(),
|
||||
!s.eval::<bool>("return QuestFrame:IsVisible()").unwrap(),
|
||||
"Goodbye closes the questgiver window"
|
||||
);
|
||||
}
|
||||
@@ -375,10 +369,10 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestAcceptButton:IsShown()")
|
||||
.eval::<bool>("return QuestAcceptButton:IsShown()")
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestDeclineButton:IsShown()")
|
||||
.eval::<bool>("return QuestDeclineButton:IsShown()")
|
||||
.unwrap());
|
||||
|
||||
s.resolve();
|
||||
@@ -434,10 +428,10 @@ fn detail_panel_action_buttons_resolve_to_real_onscreen_rects() {
|
||||
"instant text keeps the writing sound: one scratch on the wake tick"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestAcceptButton:IsEnabled()")
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled()")
|
||||
.unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return BenillaQuestDetailTextAlphaFrame:GetAlpha()")
|
||||
s.eval::<f32>("return QuestDetailTextAlphaFrame:GetAlpha()")
|
||||
.unwrap(),
|
||||
1.0,
|
||||
"the block snaps straight to opaque — no QUESTINFO_FADE_IN ramp in instant mode"
|
||||
@@ -493,7 +487,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
"the write-on scratches the WriteQuest quill each tick"
|
||||
);
|
||||
assert!(!s
|
||||
.eval::<bool>("return BenillaQuestAcceptButton:IsEnabled()")
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled()")
|
||||
.unwrap());
|
||||
|
||||
// Half a second more runs the 13-char gradient off (40 chars/s): Accept wakes and the block
|
||||
@@ -501,10 +495,10 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
s.tick(0.5);
|
||||
assert!(s.errors().is_empty(), "tick errors: {:?}", s.errors());
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestAcceptButton:IsEnabled()")
|
||||
.eval::<bool>("return QuestAcceptButton:IsEnabled()")
|
||||
.unwrap());
|
||||
let alpha = s
|
||||
.eval::<f32>("return BenillaQuestDetailTextAlphaFrame:GetAlpha()")
|
||||
.eval::<f32>("return QuestDetailTextAlphaFrame:GetAlpha()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
alpha < 1.0,
|
||||
@@ -512,7 +506,7 @@ fn write_on_still_fades_when_instant_text_is_off() {
|
||||
);
|
||||
s.tick(1.1);
|
||||
assert_eq!(
|
||||
s.eval::<f32>("return BenillaQuestDetailTextAlphaFrame:GetAlpha()")
|
||||
s.eval::<f32>("return QuestDetailTextAlphaFrame:GetAlpha()")
|
||||
.unwrap(),
|
||||
1.0,
|
||||
"the fade completes at opaque"
|
||||
@@ -544,7 +538,7 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
assert_eq!(
|
||||
s.eval::<String>("return getglobal('BenillaQuestNpcNameText'):GetText() or ''")
|
||||
s.eval::<String>("return getglobal('QuestNpcNameText'):GetText() or ''")
|
||||
.unwrap(),
|
||||
"Marshal McBride",
|
||||
"the panel-open event's arg1 lands in the title bar"
|
||||
@@ -566,14 +560,13 @@ fn npc_name_reaches_the_title_bar_on_open_and_on_refresh() {
|
||||
);
|
||||
|
||||
// The late-name path: open with an empty name, the refresh brings it.
|
||||
s.run("getglobal('BenillaQuestNpcNameText'):SetText('')")
|
||||
.unwrap();
|
||||
s.run("getglobal('QuestNpcNameText'):SetText('')").unwrap();
|
||||
s.fire_event(
|
||||
"QUEST_ITEM_UPDATE",
|
||||
vec![ScriptValue::Str("Marshal McBride".into())],
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return getglobal('BenillaQuestNpcNameText'):GetText() or ''")
|
||||
s.eval::<String>("return getglobal('QuestNpcNameText'):GetText() or ''")
|
||||
.unwrap(),
|
||||
"Marshal McBride",
|
||||
"the QUEST_ITEM_UPDATE refresh also carries the name"
|
||||
@@ -633,7 +626,7 @@ fn greeting_panel_title_rows_grow_to_their_wrapped_titles() {
|
||||
|
||||
let row = |i: u32| -> (f32, f32, f32) {
|
||||
s.eval::<(f32, f32, f32)>(&format!(
|
||||
"local b = getglobal('BenillaQuestTitleButton{i}')\n\
|
||||
"local b = getglobal('QuestTitleButton{i}')\n\
|
||||
return b:GetTop(), b:GetBottom(), b:GetFontString():GetStringHeight()"
|
||||
))
|
||||
.unwrap()
|
||||
@@ -715,15 +708,14 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// The regression this fork could break: a PLAIN click still picks the choice (the ref's third
|
||||
// arm). Row 2 is picked here so the modified clicks below — aimed at row 1 — would visibly
|
||||
// move it if they leaked into the select arm.
|
||||
s.run("BenillaQuestRewardChoice2:Click()").unwrap();
|
||||
s.run("QuestRewardChoice2:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return BenillaQuestFrame.itemChoice")
|
||||
.unwrap(),
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
2,
|
||||
"a plain click still selects the reward choice"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return BenillaQuestRewardChoiceHighlight:IsShown()")
|
||||
.eval::<bool>("return QuestRewardChoiceHighlight:IsShown()")
|
||||
.unwrap());
|
||||
assert!(
|
||||
s.take_dressup_intents().is_empty(),
|
||||
@@ -733,7 +725,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// SHIFT + chat open → the row's full escaped link, and the selection is untouched.
|
||||
assert!(s.focus_editbox("ChatFrameEditBox"));
|
||||
s.set_modifiers(true, false, false);
|
||||
s.run("BenillaQuestRewardChoice1:Click()").unwrap();
|
||||
s.run("QuestRewardChoice1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
@@ -742,16 +734,15 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
"shift-click posted choice 1's link"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return BenillaQuestFrame.itemChoice")
|
||||
.unwrap(),
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
2,
|
||||
"the shift arm returns — it must NOT also select the clicked choice"
|
||||
);
|
||||
|
||||
// A FIXED reward row (plain BenillaQuestItemTemplate → BenillaQuestItem_OnClick) posts too.
|
||||
// A FIXED reward row (plain QuestItemTemplate → QuestItem_OnClick) posts too.
|
||||
s.run("ChatFrameEditBox:SetText(\"\")").unwrap();
|
||||
s.set_modifiers(true, false, false);
|
||||
s.run("BenillaQuestRewardReward1:Click()").unwrap();
|
||||
s.run("QuestRewardReward1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return ChatFrameEditBox:GetText()")
|
||||
@@ -762,7 +753,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
// CTRL → the dressing room wearing the clicked reward, and STILL no reselect. The intent pair
|
||||
// is ordered: Dress (the room was closed, so it re-dresses in the player's own gear) then TryOn.
|
||||
s.set_modifiers(false, true, false);
|
||||
s.run("BenillaQuestRewardChoice1:Click()").unwrap();
|
||||
s.run("QuestRewardChoice1:Click()").unwrap();
|
||||
s.set_modifiers(false, false, false);
|
||||
assert_eq!(
|
||||
s.take_dressup_intents(),
|
||||
@@ -773,8 +764,7 @@ fn reward_rows_preview_and_post_without_selecting_the_choice() {
|
||||
"ctrl-click opened the room wearing choice 1"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return BenillaQuestFrame.itemChoice")
|
||||
.unwrap(),
|
||||
s.eval::<i64>("return QuestFrame.itemChoice").unwrap(),
|
||||
2,
|
||||
"the ctrl arm returns — it must NOT also select the clicked choice"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user